0

In the following code I have a simple trait, A, and a struct Foo that implements A...

Next, I define a function that takes a reference to a trait object. From main() I pass in a reference to a concrete Foo. This works fine and successfully calls the method in the trait.

trait A {
    fn do_something(&self) {
        println!("Doing something.");
    }
}

struct Foo {}
impl A for Foo {}

fn main() {
    let foo = Foo {};
    make_do_something(&foo);
}

fn make_do_something(a: &dyn A) {
    a.do_something();
}

What is the Rust compiler doing here? Is the concrete Foo reference automatically coerced to a Trait object reference? That is, is a new trait object being created on the heap that is referring to my concrete object? I can't find a clear description of how this works in the documentation.

Chuck
  • 1,850
  • 2
  • 17
  • 28
  • 1
    Yes, they're automatically coerced. This is one form of "Unsized Coercion". https://doc.rust-lang.org/reference/type-coercions.html#unsized-coercions – PitaJ Apr 13 '23 at 22:01
  • Thanks @PitaJ. That reference helps. Maybe this question doesn't belong here but I'm not 100% certain why trait objects are unsized. That is, I figure a trait object should always contain exactly 2 pointers - a pointer to the vtable and a pointer to the concrete object. So I'm not sure why you always need &dyn Foo or Box instead of just dyn Foo. Like why can't a dyn Foo, which is exactly 2 pointers in size live directly on the stack? I'm sure I'm missing some sublety. – Chuck Apr 13 '23 at 22:16
  • 2
    A trait object `dyn Foo` is not itself a pointer, it is just a type (and one of dynamic size, etc). Only when combined with a reference or smart pointer (like `&dyn Foo` or `Box`) does it get handled as you describe. `&dyn Foo` is exactly two pointers that live on the stack. – PitaJ Apr 13 '23 at 22:50
  • Thanks again, @PitaJ. Your answer and the answer below from Kevin Reid has clarified this for me. – Chuck Apr 14 '23 at 00:28

1 Answers1

2

Is the concrete Foo reference automatically coerced to a Trait object reference?

Yes, the &Foo is coerced to &dyn A. The &Foo consists of a pointer to the data of the Foo (which happens to be zero bytes long in your example, but this makes no difference); the &dyn A consists of that pointer and also a pointer to the vtable generated from impl A for Foo.

is a new trait object being created on the heap

No; a trait object demands additional data to refer to it, not to store it.

Compare this with the other main kind of dynamically-sized (!Sized) type in Rust, the slice: a &Foo contains a pointer to a Foo, and a &[Foo] contains that pointer and a length. They might be the same data pointed to (you can convert both ways), but the pointer is different.

We can say in general that pointing to a value of a Sized type requires only the machine pointer to the data; pointing to a value of a !Sized type requires additional information (called the “metadata”).

Every pointer to a dynamically-sized type is constructed either by a coercion, in which case the compiler inserts the necessary data that is known at compile time (vtable for a trait object, or length for a slice pointer coerced from an pointer array), or by library code which knows something about what it's constructing a pointer for (e.g. when Vec<T> produces a &[T]).

Kevin Reid
  • 37,492
  • 13
  • 80
  • 108