sundegan
sundegan

Reputation: 23

Why are trait objects usually used via references (&dyn Trait) or smart Pointers (like Box<dyn Trait>)?

In Rust, why are trait objects usually used via references (&dyn Trait) or smart Pointers (like Box<dyn Trait>)? Does it have to be? Or is it better to use it this way?

Upvotes: 0

Views: 134

Answers (1)

kmdreko
kmdreko

Reputation: 60497

They are handled via references and smart pointers because they won't compile otherwise:

trait Foo {}
struct Bar;
impl Foo for Bar {}

fn main() {
    let f1: dyn Foo = Bar;
    let f2: dyn Foo = Bar as dyn Foo;
}
error[E0308]: mismatched types
 --> src/main.rs:6:23
  |
6 |     let f1: dyn Foo = Bar;
  |             -------   ^^^ expected `dyn Foo`, found `Bar`
  |             |
  |             expected due to this
  |
  = note: expected trait object `dyn Foo`
                   found struct `Bar`
  = help: `Bar` implements `Foo` so you could box the found value and coerce it to the trait object `Box<dyn Foo>`, you will have to change the expected type as well

error[E0620]: cast to unsized type: `Bar` as `dyn Foo`
 --> src/main.rs:7:23
  |
7 |     let f2: dyn Foo = Bar as dyn Foo;
  |                       ^^^^^^^^^^^^^^
  |
help: consider using a box or reference as appropriate
 --> src/main.rs:7:23
  |
7 |     let f2: dyn Foo = Bar as dyn Foo;
  |                       ^^^

error[E0277]: the size for values of type `dyn Foo` cannot be known at compilation time
 --> src/main.rs:6:9
  |
6 |     let f1: dyn Foo = Bar;
  |         ^^ doesn't have a size known at compile-time
  |
  = help: the trait `Sized` is not implemented for `dyn Foo`
  = note: all local variables must have a statically known size
  = help: unsized locals are gated as an unstable feature
help: consider borrowing here
  |
6 |     let f1: &dyn Foo = Bar;
  |             +

A dyn Trait is a dynamic type that represents any potential concrete type that implements that Trait. Since it is representing a variety of types of potentially different sizes, it itself does not have a fixed size. This is called a dynamically sized type (DST).

Because they don't have a fixed size, there are limits to where we can put them. In particular a DST cannot be stored in a variable; and if stored in a struct, it makes that struct a DST which therefore also cannot be stored in a variable. There is a unsized locals RFC that may make it possible in the future, but right now its fate is unclear.

Side note: you can still reference a variable as a dynamic trait but that is because the variable is still a known concrete type even though the trait object accesses it dynamically:

fn main() {
    let b = Bar;
    let f: &dyn Foo = &b;
}

So in order to work with a DST, it requires some indirection; the value must be stored elsewhere and only referred to through a reference or smart pointer like Box (Rc, Arc, also work).


Technically tangential but worth mentioning anyway: references and smart pointers to trait objects are "fat". This means they actually store two pointers: one to the data and another to a v-table that maps to the trait functions associated with that trait implementation. See What is a "fat pointer"? for more info.

Upvotes: 4

Related Questions