Impl Generic struct with field from impl Trait type

Hello,
I'm stuck at understanding impl Trait return type.

From what I understand

fn get_pet() -> impl Bark { ... }

returns some struct that implements that trait...

Structs are also able to store traits without Boxing them with the help of generics, so it creates a copy for each possible implementation.

struct Owner<T: Bark> { pet: T }

I can create struct with pet from get_pet function like this:

Owner{ pet: get_pet() }; 

What I don't understand is, why I'm unable to use this inside impl function of that struct like this:

impl<T> Owner<T> where T: Bark {
    fn new() -> Self {
        Owner { pet: get_dog() }
    }
}

Can someone please explain to me, why this is not allowed and if there is any solution to this?
I wanted to avoid Boxing struct fields as this seemed cleaner, but I'm unable to make it work.

Complete example: Rust Playground

Your implementation has a free type parameter T, which can be any type that implements Bark. That type might not be the one returned from get_dog. What you need isn’t a method that returns Self, which includes T by definition, but one that returns Owner<impl Bark>. One way to do that is this:

trait Bark {}
struct Doggy{}
impl Bark for Doggy {}

struct Owner<T> { pet: T }

fn get_dog() -> impl Bark {
    Doggy{}
}

fn main() { 
    Owner{pet: Doggy{} }; 
    Owner{pet: get_dog() };
}

impl<T> Owner<T> where T: Bark {
    // Most methods go here
}

impl Owner<()> {
    fn new() -> Owner<impl Bark> {
        Owner { pet: get_dog() }
    }
}

(Playground)

2 Likes

First, to be sure you understand, impl Trait in the return position still must resolve to a single concrete type, it is not monomorphized like Owner<T>.

struct FirTree;
impl Bark for FirTree {}

// Errors: `if` and `else` have incompatible types
fn get_dog() -> impl Bark {
    if true {
        Doggy{}
    } else {
        FirTree
    }
}

Next, let's look at how the error changes if we make the new function return a Doggy directly:

// Errors: expected type parameter `T`, found struct `Doggy`
impl<T> Owner<T> where T: Bark {
    fn new() -> Self {
        Owner { pet: Doggy {} }
    }
}

This is the same problem as your opaque type error, except the type isn't opaque anymore. We said we were returning Self, which in that context is an Owner<T>, but returned an Owner<Doggy> instead. (Consider the mismatch if you called Owner::<FirTree>::new().)

Incidentally "opaque" here means that the type returned from an -> impl Trait cannot be named, other traits it may incidentally implement cannot be used, and so on. Among other considerations, this gives the function author the flexibility to change the underlying type, or return types which incorporate other unnameable types such as closures, etc.

@2e71828 showed a way to return an Owner<impl Bark>, so I won't cover that. You still can't count on it being a Doggy (or any other specific type) though, as it's still opaque.

3 Likes

Ah, thank you very much both.
I could not grasp why T and Impl could not be the same and

Owner::<FirTree>::new()

was my 'aha moment'. Seeing example like that helps a lot, thank you.