How to make this code work?

I am just thinking randomly

trait Trait {}
struct S;
impl Trait for S {}
fn main() {
  let a : dyn Trait = // what to write here to be compiled ?
}

Box your trait objects:

trait Trait {}
struct S;
impl Trait for S {}
fn main() {
  let a : Box<dyn Trait> = Box::new(S);
}

We need to heap allocate since we don't necessarily have a fixed memory footprint for all struct T's that implement Trait. As such, stack allocation (i.e., no box) would be bad since it'd lead to possibly fragmented stack memory (fragmentation on the stack - BAD!)

dyn Trait, just like str, cannot exist on its own. It must always be behind some level of indirection, like &dyn Trait or Box<dyn Trait> or Arc<dyn Trait>.

That's because dyn Trait has variable size, which could be anything from 0 to 9223372036854775807 bytes, so the compiler doesn't know how much space to reserve for it. If you put it behind something pointer-like, then it's clear, because its size becomes the pointer size. It also needs indirection to have a place to store its vtable pointer.

2 Likes

Does that mean &dyn Trait has a larger size than &T for some concrete type T like u32 or String. Because the vtable pointer is also included?

Not sure if I should care but am still curious :slightly_smiling_face:

The width of a pointer is 64-bits (on 64-bit processors). This means the width of &10u32 is larger than 10u32 itself on 64-bit processors, but equal on 32-bit processors.

Yes, a &dyn Trait is larger than a reference to a sized type.

3 Likes

@yushang @drmason13 if you guys would like a thorough explanation about all things sizedness related in Rust I highly recommend reading Sizedness In Rust

3 Likes

Finally I found 2 ways to deal with Trait Object without using Box

use raw pointer

trait Trait {}
struct S;
impl Trait for S {}
fn main() {
  let a = S;
  let b : *const dyn Trait = &raw const a;  
}
// the cons : 
// 1. must enclosed in unsafe
// 2. manually deref
// 3. can not take pointer of a temp object

or use reference

trait Trait {}
struct S;
impl Trait for S {}
fn main() {
  let b : &dyn Trait = &S;
}
// preferred

the root is Trait Object is unsized , but raw pointer or reference to it is sized.

Don't use raw pointers unless you really have to. It basically turns rust into C, removing the guarantees that make Rust great. You can think of & as a "rust pointer" that has security/lifetime guarantees (including a compile-time RwLock-like mechanism).

Using indirection is fine because the size of a pointer is constant (64bit on x64, 32bit on 32-bit processors, etc)

Using a reference is pretty similar to using a box. Both are a kind of pointer.

Are you sure this works, if the trait has methods on it? I'm confused as to where the reference could possibly be pointing at, if not the heap. The only thing coming to my mind is that marker traits are special in that regard.

@Phlopsi A &dyn Trait consists of two pointers: A pointer to the value, and a pointer to a vtable. You can see this by running the following:

trait MyTrait {
    fn foo(&self) -> u32;
}

fn main() {
    println!("{}", std::mem::size_of::<&u32>());
    println!("{}", std::mem::size_of::<&dyn MyTrait>());
}
8
16

playground

2 Likes

Where is the vtable stored? Is it put into the static section of the binary in the above example?

Yes. It's stored in the same places as e.g. the data in string literals.

1 Like