Выбрать главу

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

The compiler is capable of providing basic implementations for some traits via the #[derive] attribute. These traits can still be manually implemented if a more complex behavior is required.

The following is a list of derivable traits:

   • Comparison traits: Eq, PartialEq, Ord, PartialOrd.

   • Clone, to create T from &T via a copy.

   • Copy, to give a type 'copy semantics' instead of 'move semantics'.

   • Hash, to compute a hash from &T.

   • Default, to create an empty instance of a data type.

   • Debug, to format a value using the {:?} formatter.

// `Centimeters`, a tuple struct that can be compared

#[derive(PartialEq, PartialOrd)]

struct Centimeters(f64);

// `Inches`, a tuple struct that can be printed

#[derive(Debug)]

struct Inches(i32);

impl Inches {

fn to_centimeters(&self) -> Centimeters {

let &Inches(inches) = self;

Centimeters(inches as f64 * 2.54)

}

}

// `Seconds`, a tuple struct with no additional attributes

struct Seconds(i32);

fn main() {

let _one_second = Seconds(1);

// Error: `Seconds` can't be printed; it doesn't implement the `Debug` trait

//println!("One second looks like: {:?}", _one_second);

// TODO ^ Try uncommenting this line

// Error: `Seconds` can't be compared; it doesn't implement the `PartialEq` trait

//let _this_is_true = (_one_second == _one_second);

// TODO ^ Try uncommenting this line

let foot = Inches(12);

println!("One foot equals {:?}", foot);

let meter = Centimeters(100.0);

let cmp =

if foot.to_centimeters() < meter {

"smaller"

} else {

"bigger"

};

println!("One foot is {} than one meter.", cmp);

}

הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

derive

The Rust compiler needs to know how much space every function's return type requires. This means all your functions have to return a concrete type. Unlike other languages, if you have a trait like Animal, you can't write a function that returns Animal, because its different implementations will need different amounts of memory.

However, there's an easy workaround. Instead of returning a trait object directly, our functions return a Box which contains some Animal. A box is just a reference to some memory in the heap. Because a reference has a statically-known size, and the compiler can guarantee it points to a heap-allocated Animal, we can return a trait from our function!

Rust tries to be as explicit as possible whenever it allocates memory on the heap. So if your function returns a pointer-to-trait-on-heap in this way, you need to write the return type with the dyn keyword, e.g. Box<dyn Animal>.

struct Sheep {}

struct Cow {}

trait Animal {

// Instance method signature

fn noise(&self) -> &'static str;

}

// Implement the `Animal` trait for `Sheep`.

impl Animal for Sheep {

fn noise(&self) -> &'static str {

"baaaaah!"

}

}

// Implement the `Animal` trait for `Cow`.

impl Animal for Cow {

fn noise(&self) -> &'static str {

"moooooo!"

}

}

// Returns some struct that implements Animal, but we don't know which one at compile time.

fn random_animal(random_number: f64) -> Box<dyn Animal> {

if random_number < 0.5 {

Box::new(Sheep {})

} else {

Box::new(Cow {})

}

}

fn main() {

let random_number = 0.234;

let animal = random_animal(random_number);

println!("You've randomly chosen an animal, and it says {}", animal.noise());

}

הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

In Rust, many of the operators can be overloaded via traits. That is, some operators can be used to accomplish different tasks based on their input arguments. This is possible because operators are syntactic sugar for method calls. For example, the + operator in a + b calls the add method (as in a.add(b)). This add method is part of the Add trait. Hence, the + operator can be used by any implementor of the Add trait.