Method returning dyn fails to compile if the return value is assigned to a variable before returned

After struggling for hours with writing a factory method which returns instances of a struct containing a field of a generic type (implementing a trait), I find that it compiles and runs only if I do not assign the result to a variable before returning it (see the bottom of the post for a complete working example):

This compiles and runs:

fn compiles() -> Parent<dyn IsAChild> {
    return match rand::thread_rng().gen_bool(1.0/2.0) {
        true => Parent { child: Box::new(Daughter{} ) },
        false => Parent { child: Box::new(Son{} ) },
    }
}

This does not compile:

fn does_not_compile() -> Parent<dyn IsAChild> {
    return match rand::thread_rng().gen_bool(1.0/2.0) {
        true => {
            let parent = Parent { child: Box::new(Daughter {}) };
            parent
        },
        false => {
            let parent = Parent { child: Box::new(Son {}) };
            parent
        },
    }

}

The does_not_compile() function results in the following error:

error[E0308]: mismatched types
  --> src\main.rs:39:13
   |
39 |             parent
   |             ^^^^^^ expected trait object `dyn IsAChild`, found struct `Daughter`
   |
   = note: expected struct `Parent<(dyn IsAChild + 'static)>`
              found struct `Parent<Daughter>`

This has me completely stumped. To me there is no semantic difference between the two, only the fact that the result is (temporarily) stored in a variable before it is returned. What is going on here?

Complete example (add rand = "*" to the dependencies):

use rand::Rng;

trait IsAChild {}

struct Parent<T: IsAChild + ?Sized> {
    child: Box<T>
}

struct Son;
impl IsAChild for Son {}

struct Daughter;
impl IsAChild for Daughter {}

fn compiles() -> Parent<dyn IsAChild> {
    return match rand::thread_rng().gen_bool(1.0/2.0) {
        true => Parent { child: Box::new(Daughter{} ) },
        false => Parent { child: Box::new(Son{} ) },
    }
}

fn compiles_too() -> Parent<dyn IsAChild> {
    return match rand::thread_rng().gen_bool(1.0/2.0) {
        true => {
            println!("It's a girl!");
            Parent { child: Box::new(Daughter{} ) }
        },
        false => {
            println!("It's a boy!");
            Parent { child: Box::new(Son{} ) }
        },
    }
}

/*
fn does_not_compile() -> Parent<dyn IsAChild> {
    return match rand::thread_rng().gen_bool(1.0/2.0) {
        true => {
            let parent = Parent { child: Box::new(Daughter {}) };
            parent
        },
        false => {
            let parent = Parent { child: Box::new(Son {}) };
            parent
        },
    }
}
*/

fn main() {
    compiles();
}    
    fn does_not_compile() -> Parent<dyn IsAChild> {
        return match rand::thread_rng().gen_bool(1.0/2.0) {
            true => {
                let parent = Parent { child: Box::new(Daughter {}) };
                parent
            },
            false => {
                let parent = Parent { child: Box::new(Son {}) };
                parent
            },
        }
    
    }
    fn main() {
        compiles();
    }

>Solution :

The difference here is type inference. In the first example because the expression is the return value, the compiler looks to the function signature to determine the generic parameter of Parent, which it decides is dyn IsAChild. In the second example, the compiler needs to infer the type of the variable parent. It first looks at the type of the expression it is assigned to, and the type of Parent { child: Box::new(Daughter {}) } is unambiguously Parent<Daughter>, so the variable is given that type instead of Parent<dyn IsAChild>. You could fix th second example by explicitly saying

let parent: Parent<dyn IsAChild> = Parent { child: Box::new(Daughter {}) };

The compiler only coerces the contents of the Box to an unsized type if it has to, so that’s why the example with the variable needs to be explicit.

Leave a Reply