Search code examples
rusttrait-objects

Why does using Option::map to Box::new a trait object not work?


trait FooTrait {}

struct FooStruct;

impl FooTrait for FooStruct {}

fn main() {
    let maybe_struct: Option<dyn FooStruct> = None;

    //  Does not compile
    let maybe_trait: Option<Box<dyn FooTrait>> = maybe_struct.map(Box::new);

    // Compiles fine
    let maybe_trait: Option<Box<dyn FooTrait>> = match maybe_struct {
        Some(s) => Some(Box::new(s)),
        None => None,
    };
}
error[E0404]: expected trait, found struct `FooStruct`
 --> src/main.rs:9:34
  |
9 |     let maybe_struct: Option<dyn FooStruct> = None;
  |                                  ^^^^^^^^^ not a trait

Rustc 1.23.0. Why doesn't the first approach compile? Am I missing something obvious, or... huh?


Solution

  • Box::new only works with sized types; that is, it takes a value of a sized type T and returns Box<T>. In certain places a Box<T> can be coerced into a Box<U> (if T: Unsize<U>).

    Such coercion does not happen in .map(Box::new), but does in Some(Box::new(s)); the latter is basically the same as Some(Box::new(s) as Box<FooTrait>).

    You could create (in nightly) your own box constructor that returns boxes of unsized types like this:

    #![feature(unsize)]
    
    fn box_new_unsized<T, U>(v: T) -> Box<U>
    where
        T: ::std::marker::Unsize<U>,
        U: ?Sized,
    {
        Box::<T>::new(v)
    }
    

    and use it like .map(box_new_unsized). See Playground.