Search code examples
rusttrait-objects

Why can't trait methods have a default implementation returning self as a trait object


I am new to rust and trying to wrap my head around this error. If i use write this code

pub struct A {}

pub trait Trait {
    fn stuff(self: Box<Self>) -> Box<dyn Trait>;
}

impl Trait for A {
    fn stuff(self: Box<Self>) -> Box<dyn Trait> {
        self
    }
}

It compiles fine.

But if i write this

pub struct A {}

pub trait Trait {
    fn stuff(self: Box<Self>) -> Box<dyn Trait> {
        self
    }
}

impl Trait for A {}

I get error

error[E0277]: the size for values of type `Self` cannot be known at compilation time
 --> src/lib.rs:5:9
  |
5 |         self
  |         ^^^^ doesn't have a size known at compile-time
  |
  = note: required for the cast from `Box<Self>` to `Box<(dyn Trait + 'static)>`
help: consider further restricting `Self`
  |
4 |     fn stuff(self: Box<Self>) -> Box<dyn Trait> where Self: Sized {
  |                                                 +++++++++++++++++

Why must self be sized when implemented as default but not when implemented in the struct? In both cases, i am using Box pointer so size is not really needed since no matter what goes in the type argument, Box will always have the same size.


Solution

  • You can provide a default implementation returning Box<Self> as Box<dyn Trait> by constraining Self to be Sized and 'static:

    pub trait Trait {
        fn stuff(self: Box<Self>) -> Box<dyn Trait>
        where
            Self: Sized + 'static,
        {
            self
        }
    }
    

    It requires 'static because that is the inferred lifetime of Box<dyn Trait>. There is no lifetime (elided or otherwise) in the method parameters that a shorter one could be derived from anyway. Self could have lifetimes associated with it, but they can't affect the trait signature, so therefore Self must adhere to the trait constraints.

    It requires Sized due to how trait objects like Box<dyn Trait> are implemented. If you have a boxed sized type (Box<T>) then that is represented by a single pointer. If you have a boxed unsized type (Box<[T]>, Box<dyn Trait>, Box<str>, etc.) then it is represented by two pointers: one to the value and another for metadata (a v-table for trait objects or a length for slices). Also known as a "fat pointer".

    If Self were not Sized and you tried to add another level of unsized-ness to it by coercing it into a Box<dyn Trait>, then either it would need to be a "fat fat pointer" which would be incoherent since Box<dyn Trait> itself could have two different sizes, or it would need to lose a bit of metadata which of course Rust isn't going to do.

    See Why can't `&(?Sized + Trait)` be cast to `&dyn Trait`? for a more in-depth explanation.