1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2025-03-31 09:42:11 +02:00
comprehensive-rust/src/generics/trait-objects.md
Dustin J. Mitchell 780368b4f7
Minor fixes for Day 3 Morning (#532)
* don't explain default trait methods early

* talk about Iterator before IntoIterator

* Defer discussion of trait objects to that chapter

* be more specific about turbofish, in speaker notes
2023-03-28 15:42:56 -04:00

4.0 KiB

Trait Objects

We've seen how a function can take arguments which implement a trait:

use std::fmt::Display;

fn print<T: Display>(x: T) {
    println!("Your value: {x}");
}

fn main() {
    print(123);
    print("Hello");
}

However, how can we store a collection of mixed types which implement Display?

fn main() {
    let displayables = vec![123, "Hello"];
}

For this, we need trait objects:

use std::fmt::Display;

fn main() {
    let displayables: Vec<Box<dyn Display>> = vec![Box::new(123), Box::new("Hello")];
    for x in displayables {
        println!("x: {x}");
    }
}

Memory layout after allocating displayables:

 Stack                             Heap
.- - - - - - - - - - - - - -.     .- - - - - - - - - - - - - - - - - - - - - - - -.
:                           :     :                                               :
:    displayables           :     :                                               :
:   +-----------+-------+   :     :   +-----+-----+                               :
:   | ptr       |   o---+---+-----+-->| o o | o o |                               :
:   | len       |     2 |   :     :   +-|-|-+-|-|-+                               :
:   | capacity  |     2 |   :     :     | |   | |   +----+----+----+----+----+    :
:   +-----------+-------+   :     :     | |   | '-->| H  | e  | l  | l  | o  |    :
:                           :     :     | |   |     +----+----+----+----+----+    :
`- - - - - - - - - - - - - -'     :     | |   |                                   :
                                  :     | |   |     +-------------------------+   :
                                  :     | |   '---->| "<str as Display>::fmt" |   :
                                  :     | |         +-------------------------+   :
                                  :     | |                                       :
                                  :     | |   +----+----+----+----+               :
                                  :     | '-->| 7b | 00 | 00 | 00 |               :
                                  :     |     +----+----+----+----+               :
                                  :     |                                         :
                                  :     |     +-------------------------+         :
                                  :     '---->| "<i32 as Display>::fmt" |         :
                                  :           +-------------------------+         :
                                  :                                               :
                                  :                                               :
                                  '- - - - - - - - - - - - - - - - - - - - - - - -'

Similarly, you need a trait object if you want to return different types implementing a trait:

fn numbers(n: i32) -> Box<dyn Iterator<Item=i32>> {
    if n > 0 {
        Box::new(0..n)
    } else {
        Box::new((n..0).rev())
    }
}

fn main() {
    println!("{:?}", numbers(-5).collect::<Vec<_>>());
    println!("{:?}", numbers(5).collect::<Vec<_>>());
}

  • Types that implement a given trait may be of different sizes. This makes it impossible to have things like Vec<Display> in the example above.
  • dyn Display is a way to tell the compiler about a dynamically sized type that implements Display.
  • In the example, displayables holds fat pointers to objects that implement Display. The fat pointer consists of two components, a pointer to the actual object and a pointer to the virtual method table for the Display implementation of that particular object.
  • Compare these outputs in the above example:
        use std::fmt::Display;
        println!("{}", std::mem::size_of::<u32>());
        println!("{}", std::mem::size_of::<&u32>());
        println!("{}", std::mem::size_of::<&dyn Display>());
        println!("{}", std::mem::size_of::<Box<dyn Display>>());