Search code examples
rusttraitssmart-pointerstrait-objects

Why sized trait is required for a builder function to generate Rc<T>?


This code works fine (playground):

use std::rc::Rc;

trait Foo {
    fn foo(&self);
}

struct Bar<T> {
    v: Rc<T>,
}

impl<T> Bar<T> where
T: Foo {
    fn new(rhs: Rc<T>) -> Bar<T> {
        Bar{v: rhs}
    }
}

struct Zzz {
}

impl Zzz {
    fn new() -> Zzz {
        Zzz{}
    }
}

impl Foo for Zzz {
    fn foo(&self) {
        println!("Zzz foo");
    }
}

fn make_foo() -> Rc<Foo> {
    Rc::new(Zzz{})
}

fn main() {
    let a = Bar::new(Rc::new(Zzz::new()));
    a.v.as_ref().foo()
}

but if I make a wrapper to generate Rc like below, the compiler complains about missing sized trait (playground)

fn make_foo() -> Rc<dyn Foo> {
    Rc::new(Zzz::new())
}

fn main() {
    let a = Bar::new(make_foo());
    a.v.as_ref().foo()
}

in both cases, Bar::new received parameters with same type Rc, why the rust compiler reacts different?


Solution

  • By default, all type variables are assumed to be Sized. For example, in the definition of the Bar struct, there is an implicit Sized constraint, like this:

    struct Bar<T: Sized> {
        v: Rc<T>,
    }
    

    The object dyn Foo cannot be Sized since each possible implementation of Foo could have a different size, so there isn't one size that can be chosen. But you are trying to instantiate a Bar<dyn Foo>.

    The fix is to opt out of the Sized trait for T:

    struct Bar<T: ?Sized> {
        v: Rc<T>,
    }
    

    And also in the context of the implementations:

    impl<T: ?Sized> Bar<T>
    where 
        T: Foo 
    

    ?Sized is actually not a constraint, but relaxing the existing Sized constraint, so that it is not required.

    A consequence of opting out of Sized is that none of Bar's methods from that impl block can use T, except by reference.