Why can't assign a struct implementing a trait to a generic constrained by the trait

I know I am probably missing a very important concept here but until now I never had any problems with generics or traits implementations... maybe I was just lucky. :slight_smile: Anyway, here is the (somewhat simplified) code:

trait Get {
    type Item;

    fn get(&self) -> Self::Item;
}

struct Bag<'a, C> {
    item: &'a C,
}

impl<'a, C> Get for Bag<'a, C> {
    type Item = &'a C;

    fn get(&self) -> Self::Item {
        self.item
    }
}

struct X<C> {
    container: C,
}

impl<'a, C> X<C>
where
    C: Get<Item = &'a str> + Copy,
{
    fn from_bag(bag: Bag<'a, C>) -> Self {
        Self { container: bag }
    }
}

Can anybody explain why I cannot assign bag to the container field even if Bag implements Get for any possible C? The exact compiler error is:

error[E0308]: mismatched types
  --> sandbox/src/main.rs:28:27
   |
23 | impl<'a, C> X<C>
   |          - expected this type parameter
...
28 |         Self { container: bag }
   |                           ^^^ expected type parameter `C`, found `Bag<'_, C>`
   |
   = note: expected type parameter `C`
                      found struct `Bag<'a, C>`

Can anybody explain why I cannot assign bag to the container field even if Bag implements Get for any possible C?

Well, sure: the type of bag is Bag<'a, C>, and you're trying to assign it to a field of type C. Those are two different types.

If you call .get(), like this,

        Self { container: *bag.get() }

then it works. The * is necessary because get returns a reference &'a C, .

If you want the container field of X<C> to be a Bag<'b, B> that implements Get<Item = &'a str>, that would mean

  • C = Bag<'b, V> (since container in X<C> has type C)
  • 'b = 'a and 'B = str (since Bag<'a, str> implements Get<Item = &'a str>)

So there's no need for generics and you could just

impl<'a> X<Bag<'a, str>> {
    fn from_bag(container: Bag<'a, str>) -> Self {
        Self { container }
    }
}

But if you try this you'll get an error, as I overlooked a detail. Type parameters have a default Sized bound, but str is not Sized, so C cannot be str in Bag<'a, C> unless we relax that constraint:

-struct Bag<'a, C> {
+struct Bag<'a, C: ?Sized> {
    item: &'a C,
}

-impl<'a, C> Get for Bag<'a, C> {
+impl<'a, C: ?Sized> Get for Bag<'a, C> {

And now it works.

But that's a pretty specific method; you might as well do this IMO.

impl<C> From<C> for X<C> {
    fn from(container: C) -> Self {
        Self { container }
    }
}

Let me try to tackle why your OP can't work from another angle.

impl<'a, C> X<C>
where
    C: Get<Item = &'a str> + Copy,
{
    fn from_bag(bag: Bag<'a, C>) -> Self {
        Self { container: bag }
    }
}

Straight off, container in X<C> has type C but you're assigning Bag<'a, C>. Rust doesn't unify types unless you use an associated type equality bound, or to normalize associated types. That is, it's not going to look at the implementations and decide "well this generic C must be this other thing". Among other considerations, that would break down when you added more implementations.

But let's say it did try to do such things -- this still couldn't work. If C = Bag<'a, C>, then

Bag<'a, C> = Bag<'a, Bag<'a, C>> = Bag<'a, Bag<'a, Bag<'a, C>>> = ...

And that is an impossible type.

First of all, thank you for the answers.

Maybe my example was a little simplistic. I have multiple implementations of bag (lets call them Bag1, Bag2 and so on) and I want to be able to assign them to the container field and call get() on them. The only way I can think of is to use a trait, like Get. Both answers don't work for me: @jorendorff I don't want to assign the result of get() to container I want to assign the "bag"; @quinedot you are replacing the trait + constraint with the concrete implementation but that won't work in case of multiple "bags", all implementing Get. At least, this is what I understand. Also, I'd like to NOT use a dyn Get, if possible at all.

Maybe you mean this:

impl<'a, C> X<C>
where
    C: Get<Item = &'a str> + Copy,
{
    fn from_bag_or_another_get_implementor(container: C) -> Self {
        Self { container }
    }
}

In which case, I still think you should just do this:

impl<C> From<C> for X<C> {
    fn from(container: C) -> Self {
        Self { container }
    }
}

And then where you actually need the Get functionality, you do something like this

impl<'a, C> X<C>
where
    C: Get<Item = &'a str> + Copy,
{
    fn use_the_field(&self) -> &'a str {
        self.container.get()
    }
}

If not, a less minimized playground may help convey what you're trying to do.

Yes, probably trying to minimize the example wasn't a good idea. I'll setup a playground with a full working example. Thanks again.

The full example is available on the playground (Rust Playground) but it does not build because it depends on a crate that is not available there.

Anyway, I use the shipyard ECS and I want to be able to implement an iterator on two different types: View and ViewMut in a way that allow me to write the code only once (I'll have multiple iterators and I don't want to duplicate all that code). It seems that a generic parameter with a constraint (C: Get<Out = &'a Target> + Copy) is the right thing but when I try to assign to it the concrete type that implements the trait the compiler throws an error. The error is in the implementation of the TargetsOperations trait, where I try to assign targets: self.0.

I hope this clarifies what I am trying to do.

The fundamental problem here is that you're always trying to return a value of type C = View<'a, Target>, regardless of the actual type C that the caller asked for. For example, you have a type mismatch if the user asks for the trait TargetsOperations<'a, ViewMut<'a, Target>>.

The usual fix for this sort of problem is to use an associated type instead of a type parameter, so that the actual type is determined by the implementor instead of the caller:

pub trait TargetsOperations<'a>
{
    type Getter: Get<Out = &'a Target> + Copy;
    fn targets(&self, id: EntityId) -> TargetIter<'a, Self::Getter>;
}

impl<'a> TargetsOperations<'a> for TargetsView<'a>
{
    type Getter = View<'a, Target>;
    fn targets(&self, id: EntityId) -> TargetIter<'a, View<'a, Target>> {
        TargetIter {
            targets: self.0,
            current: id,
        }
    }
}
1 Like

Alternatively, use concrete types and not generics in your implementations.

impl<'a> TargetsOperations<'a, View<'a, Target>> for TargetsView<'a> {
    fn targets(&self, id: EntityId) -> TargetIter<'a, View<'a, Target>> {

Which approach to use? If any implementor is only going to return one type of TargetIter, use the associated type approach. It will be less ambiguous in places and you won't have to repeat these bounds everywhere:

where
    C: Get<Out = &'a Target> + Copy,

You only need C as a parameter to the trait if, say, TargetsView<'_> is going to implement it more than once so it can return different TargetIter<'a, C>s.

1 Like

You mean that if my return type is TargetIter<'a, C> that means that I shold return exactly C and not some struct that implements the trait Get<Out = &'a Target> + Copy? If this is the case then I was missing a very important piece of information. This is very different from other languages I used in the past like, for example, C#. Thank you for the clarification.

I see -- this is because Rust enforces generic bounds as a sort of API contract such that the code must work for any type that meets the bounds, pre-monomorphization -- before any particular types have been provided. As opposed to a templating system that allows the definition to compile, but errors if you try to instantiate it with a type that doesn't "make sense" for the body of the definition.

If you want the latter behavior, macros are probably the closest thing in Rust.


If you have a function signature like so

fn foo<T: Clone>(t: T) { /* ... */ }

Then inside the function, you can assume that T: Clone like the bound says, but other than that you can't assume anything about T -- your function body has to make sense for any and every T that can meet the bound (even if you don't know about those types -- foreign crates, types not yet invented yet...).

And callers can call foo with any T that meets the bounds and be assured it will compile.

It works similarly for implementations:

impl<'a, C> TargetsOperations<'a, C> for TargetsView<'a>
where
    C: Get<Out = &'a Target> + Copy,
{

For this to compile, the impl body can assume the bounds are met, but otherwise has to work for any and every <'a, C> that meets the bounds. And consumers of the implementation and utilize it for anything that meets the bounds too. The explicit syntax is

// <Implementor as Trait>::TraitItem
<TargetsView<'_> as TargetsOperations<'_, X>>::targets(...)
// (caller specifies whatever X they want ^)

(but even in cases without an explicit syntax, the "API contract" must hold, and is enforced at compile time).

2 Likes

Ah! I think everything is clear now. Looking back I was quite wrong at interpreting how generics with trait bounds work. I tried both your solution and both work. Thnak you very much.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.