1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2025-03-06 08:49:17 +02:00

Avoid naming struct field like trait method (#1294)

The `name` struct field was confusing because it was named the same as
the trait method. The struct fields are now disjoint from the method
names — the fact that there are two separate name spaces is not the
point of these slides.

I also dropped the zero-sized type, this is also not the main focus
here.

I also compressed the code a bit to make the unimportant structs take
up less space.

Fixes #1292.
This commit is contained in:
Martin Geisler 2023-10-04 11:52:15 +02:00 committed by GitHub
parent a883a569d0
commit 7395725edf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 56 additions and 57 deletions

View File

@ -3,37 +3,30 @@
Rust lets you abstract over types with traits. They're similar to interfaces:
```rust,editable
struct Dog { name: String, age: i8 }
struct Cat { lives: i8 } // No name needed, cats won't respond anyway.
trait Pet {
fn name(&self) -> String;
fn talk(&self) -> String;
}
struct Dog {
name: String,
}
struct Cat;
impl Pet for Dog {
fn name(&self) -> String {
self.name.clone()
}
fn talk(&self) -> String { format!("Woof, my name is {}!", self.name) }
}
impl Pet for Cat {
fn name(&self) -> String {
String::from("The cat") // No name, cats won't respond to it anyway.
}
fn talk(&self) -> String { String::from("Miau!") }
}
fn greet<P: Pet>(pet: &P) {
println!("Who's a cutie? {} is!", pet.name());
println!("Oh you're a cutie! What's your name? {}", pet.talk());
}
fn main() {
let fido = Dog { name: "Fido".into() };
greet(&fido);
let captain_floof = Cat { lives: 9 };
let fido = Dog { name: String::from("Fido"), age: 5 };
let captain_floof = Cat;
greet(&captain_floof);
greet(&fido);
}
```

View File

@ -3,76 +3,80 @@
Trait objects allow for values of different types, for instance in a collection:
```rust,editable
struct Dog { name: String, age: i8 }
struct Cat { lives: i8 } // No name needed, cats won't respond anyway.
trait Pet {
fn name(&self) -> String;
fn talk(&self) -> String;
}
struct Dog {
name: String,
}
struct Cat;
impl Pet for Dog {
fn name(&self) -> String {
self.name.clone()
}
fn talk(&self) -> String { format!("Woof, my name is {}!", self.name) }
}
impl Pet for Cat {
fn name(&self) -> String {
String::from("The cat") // No name, cats won't respond to it anyway.
}
fn talk(&self) -> String { String::from("Miau!") }
}
fn main() {
let pets: Vec<Box<dyn Pet>> = vec![
Box::new(Cat),
Box::new(Dog { name: String::from("Fido") }),
Box::new(Cat { lives: 9 }),
Box::new(Dog { name: String::from("Fido"), age: 5 }),
];
for pet in pets {
println!("Hello {}!", pet.name());
println!("Hello, who are you? {}", pet.talk());
}
}
```
Memory layout after allocating `pets`:
```bob
Stack Heap
.- - - - - - - - - - - - - -. .- - - - - - - - - - - - - - - - - - - - - - -.
: : : :
: pets : : :
: +-----------+-------+ : : +-----+-----+ :
: | ptr | o---+---+-----+-->| o o | o o | :
: | len | 2 | : : +-|-|-+-|-|-+ :
: | capacity | 2 | : : | | | | +---------------+ :
: +-----------+-------+ : : | | | '-->| name: "Fido" | :
: : : | | | +---------------+ :
`- - - - - - - - - - - - - -' : | | | :
: | | | +----------------------+ :
: | | '---->| "<Dog as Pet>::name" | :
: | | +----------------------+ :
: | | :
: | | +-+ :
: | '-->|\| :
: | +-+ :
: | :
: | +----------------------+ :
: '---->| "<Cat as Pet>::name" | :
: pets : : +----+----+----+----+ :
: +-----------+-------+ : : +-----+-----+ .->| F | i | d | o | :
: | ptr | o---+---+-----+-->| o o | o o | | +----+----+----+----+ :
: | len | 2 | : : +-|-|-+-|-|-+ `---------. :
: | capacity | 2 | : : | | | | data | :
: +-----------+-------+ : : | | | | +-------+--|-------+ :
: : : | | | '-->| name | o, 4, 4 | :
: : : | | | | age | 5 | :
`- - - - - - - - - - - - - -' : | | | +-------+----------+ :
: | | | :
: | | | vtable :
: | | | +----------------------+ :
: | | '---->| "<Dog as Pet>::talk" | :
: | | +----------------------+ :
: | | :
: | | data :
: | | +-------+-------+ :
: | '-->| lives | 9 | :
: | +-------+-------+ :
: | :
: | vtable :
: | +----------------------+ :
: '---->| "<Cat as Pet>::talk" | :
: +----------------------+ :
: :
'- - - - - - - - - - - - - - - - - - - - - - -'
```
<details>
* Types that implement a given trait may be of different sizes. This makes it impossible to have things like `Vec<Pet>` in the example above.
* `dyn Pet` is a way to tell the compiler about a dynamically sized type that implements `Pet`.
* In the example, `pets` holds *fat pointers* to objects that implement `Pet`. The fat pointer consists of two components, a pointer to the actual object and a pointer to the virtual method table for the `Pet` implementation of that particular object.
* Compare these outputs in the above example:
- Types that implement a given trait may be of different sizes. This makes it
impossible to have things like `Vec<dyn Pet>` in the example above.
- `dyn Pet` is a way to tell the compiler about a dynamically sized type that
implements `Pet`.
- In the example, `pets` is allocated on the stack and the vector data is on the
heap. The two vector elements are *fat pointers*:
- A fat pointer is a double-width pointer. It has two components: a pointer to
the actual object and a pointer to the [virtual method table] (vtable) for the
`Pet` implementation of that particular object.
- The data for the `Dog` named Fido is the `name` and `age` fields. The `Cat`
has a `lives` field.
- Compare these outputs in the above example:
```rust,ignore
println!("{} {}", std::mem::size_of::<Dog>(), std::mem::size_of::<Cat>());
println!("{} {}", std::mem::size_of::<&Dog>(), std::mem::size_of::<&Cat>());
@ -80,4 +84,6 @@ Memory layout after allocating `pets`:
println!("{}", std::mem::size_of::<Box<dyn Pet>>());
```
[virtual method table]: https://en.wikipedia.org/wiki/Virtual_method_table
</details>