Why can I declare a struct with a non-reference `dyn Trait` but not initialize it?

Hello! This is my first day writing Rust. I'm loving it so far, but have a question broadly about type erasure, trait objects and dynamic dispatch.

Assume I'm defining a struct Vehicle, with a field motor. This field should be able to take any type that implements the trait Motor -- we want to allow for different implementations/kinds of motor.

I know I can declare motor as &dyn Motor (with proper lifetime annotations) or Box<dyn Motor>. This makes sense to me: we don't know how much space our motor takes, because it could be any type that implements the trait. Therefore, we put the actual motor somewhere else, make the struct field a reference to it, and dynamically dispatch any calls at runtime (like C++ virtual).

But, in the struct declaration, Rust also allows me to create a field motor: dyn Motor without any errors. When declared like this, it is unclear to me how much space would be reserved for motor.

Only when the struct is actually initialized do we get an error ("structs must have a statically known size to be initialized").

Why does Rust not complain about the declaration?
Is there any way to initialize this that would not generate an error? Perhaps somehow I can specify that any types with trait Motor always have a fixed size?

I'm copying a short example to clarify what I'm talking about:

trait Motor {}
struct CombustionEngine {}  // could also have ElectricMotor, etc.
impl Motor for CombustionEngine {}

struct VehicleWithMotorOnHeap       { motor: Box<dyn Motor> }
struct VehicleWithBorrowedMotor<'a> { motor: &'a dyn Motor }
struct VehicleError                 { motor: dyn Motor } 
                                          // ^ why is this allowed?

fn main() {
    let (motor_a, motor_b, motor_c) = 
        (CombustionEngine {}, CombustionEngine {}, CombustionEngine {});

    let vehicle_a = VehicleWithMotorOnHeap { motor: Box::new(motor_a) };
    let vehicle_b = VehicleWithBorrowedMotor { motor: &motor_b };
    let vehicle_c = VehicleError{ motor: motor_c };
    /* Error: the size for values of type `(dyn Motor + 'static)` cannot be 
       known at compilation time within `VehicleError`, the trait `Sized` is 
       not implemented for `(dyn Motor + 'static)`
       structs must have a statically known size to be initialized */
}

Leaving room for custom DSTs. Like the link says, there's no "proper" way to construct it (safely).

Besides the generic route, you could

trait Motor {}

#[repr(transparent)]
struct VehicleError { motor: dyn Motor } 

impl From<Box<dyn Motor>> for Box<VehicleError> {
    fn from(bx: Box<dyn Motor>) -> Self {
        // SAFETY: `VehicleError` is repr(transparent) and
        // contains only `dyn Motor`
        unsafe { std::mem::transmute(bx) }
    }
}

Related.

6 Likes

There's a Sized trait in Rust for this. Just add it as a bound to your Motor trait:

trait Motor: Sized {}

Then you will get a comprehensive error at compile time when you try to use it in a struct declaration.

Why is this allowed? Because Rust's type system must be able to represent both sized and unsized types.

5 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.