Workaround for generic associated types

#1

I know this (see draft below) is currently not possible because the lack of generic associated types. Is there another way to achieve this?

A few things that are important:

  • The Data trait should have a constructor

  • The created view should have immutable access to the data but should be allowed to be mutated, e.g. through scale.

trait Data<T> {
    type View<'a>: DataView<'a, T>;
    fn new(data: Vec<T>) -> Self;
    fn view(&self) -> Self::View;
}

trait DataView<'a, T> {
    fn magic_size(&self) -> usize;
    fn transform(self) -> Self;
}

struct NativeData<T> {
    data: Vec<T>
}

impl<T> Data<T> for NativeData<T> {
    type View<'a> = NativeDataView<'a, T>;
    fn new (data: Vec<T>) -> Self { 
        Self { data }
    }
    fn view(&self) -> Self::View {
        Self::View { data: &self.data }
    }
}

struct NativeDataView<'a, T> {
    data: &'a [T],
    scale: usize
}

impl<'a, T> DataView<'a, T> for NativeDataView<'a, T> {
    fn magic_size(&self) -> usize { self.data.len() * self.scale }
    fn transform(mut self) -> Self { 
        self.scale *= 2;
        self
    }
}

(Playground)

#2

In this example, you can add a lifetime to the trait: play

http://lukaskalbertodt.github.io/2018/08/03/solving-the-generalized-streaming-iterator-problem-without-gats.html was a pretty good treatment on the subject.

#3

Yes, the I also tried the lifetime option, but it got very annoying and confusing because it “bubbles up”.
Thank you for the link. Maybe I will try the third workaround.

Are there any chances that generic associated types land in the nightly soon?

#4

Another thing you could do:

// Generic associated types
trait Gat<T> {
    type Output;
}

// Generic associated lifetimes
trait Gal<'a> {
    type Output: 'a;
}

trait NeedsGat {
    fn gat_me<T>(&self) <Self as Gat<T>>::Output where Self: Gat<T> {}
    
    fn gal_me<'a>(&'a self) <Self as Gal<'a>>::Output where Self: Gal<'a> {}
}

This is quite a bit more verbose, but it will do the same sort of things as GATs will.


Note: that the above example won’t work. My mistake, see my comment below for a fix.

#5

I tried and failed: playground

#6

When you try to make the associated type, make it in the GAT trait.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=7ab50e2b369faca908794011adaf118d

That way Rust knows what you’re doing. But you should be able to use the GAT’s associated type without issue once it is created.


The problem with doing it outside of the GAT is that Rust doesn’t know that you aren’t doing something like this

trait Gat<T> {
    type Ty;
}

impl Gat<()> for () { type Ty = u32; }
impl Gat<i32> for () { type Ty = [u32; 5]; }

Which would mean that in this

trait Hi {
    fn make_gat<T>() -> <Self as Gat<T>>::Ty where Self: Gat<T>;
}

<Self as Gat<T>>::Ty wouldn’t resolve to the same type for each T.

#7

Good point, thank you. So I tried to build the next step on top of this (here), which is the Add trait and the Test struct.
Is this possible without having a lifetime in the Add trait and especially not in the Test struct?

#8

Would this work for you?

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=36d281a4aab4e42cdef2dbe244e6ed18


here is a version that gets rid of the lifetime parameter on Add

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=43d4422a1e56a8459eab3fa1a6a913d4

#9

I think the first one will do it, thank you!

Nevertheless, do you have an idea when we can use real GAT in rust?

#10

No idea, it’s been in the works for a while so I wouldn’t wait for it.

#11

Sorry for hijacking this thread, but I have a very similar problem and was very intrigued by the solutions presented here. I just couldn’t get them to work in my case.

Here is a minimal example of what I would like to achieve, I a

trait Trait {
    fn assoc<T>() -> <Self as AssocFactory<T>>::Assoc
    where
        Self: AssocFactory<T>
    {
        Self::Assoc::new()
    }
}

trait AssocFactory<T> {
    type Assoc: Assoc<Item = T>;
}

trait Assoc: Sized {
    type Item;
    type Parent: Trait;
    
    fn new() -> Self;
}

I want types implementing Trait to be able to call assoc for any T and I also want to use Trait as an abstract interface, i.e. I can’t rely on any concrete implementations.

I tried to use blanket impls like so:

impl<T, A: Assoc<Item = T>> AssocFactory<T> for A::Parent {
    type Assoc = A;
}

or so:

impl<T, F, A> AssocFactory<T> for F where F: Trait, A: Assoc<Parent = F> {
    type Assoc = A;
}

But these fail due to E0207 (unconstrained type parameter). Is there any way I can realize the described design without GAT or existential types?

#12

No, you can’t use those blanket impls, even with GATs, because there is no way to choose what instance of Assoc to use in either blanket impl this would break UFCS if it was allowed.

trait Trait {
    fn assoc<A: Assoc<Parent = Self>>() -> A
    {
        Self::Assoc::new()
    }
}

trait Assoc: Sized {
    type Item;
    type Parent: Trait;
    
    fn new() -> Self;
}

Would this work. AssocFactory doesn’t look necessary.

#13

Thank you for your answer, but that does not solve my issue.
Perhaps I should have expressed my intent better:

The Assoc trait is meant to be implemented by some generic type that is accompanying each implementation of the Trait trait (i.e. it’s associated):

struct FooParent;
impl Trait for FooParent { ... }

struct Foo<T>(T);

impl<T> Assoc for Foo<T> {
    type Item = T;
    type Parent = FooParent;

    fn new() -> Self { ... }
}

Foo<T> would be the generic associated type for some other type FooParent that implements Trait.
Given a type T I want to able to call Trait::assoc<T>() and get an instance of the GAT (e.g. Foo<T>) created by Assoc::new().
I was hoping that by using the associated Parent type back to a Trait implementor, I could use blanket implementations to this generically, i.e. without having to rely on any concrete implementations.
The AssocFactory trait is not strictly necessary, it was just an attempt by myself to replicate the pattern you described for the original problem.

#14

I see, you implemented the pattern incorrectly. Both the trait and the GAT trait are supposed to be implemented for the same type.

like this

trait Parent {
    // stuff that uses <Self as ParentGat<T>>::child

    fn make_child_general<T>() -> <Self as ParentGat<T>>::Child
        where Self: ParentGat<T> {
        Self::make_child()
    }
}

/// This hold the value of the associated type
/// and a way to create an instance of the associated type.
trait ParentGat<T> {
    type Child;
    
    fn make_child() -> Self::Child;
}

struct FooParent;
impl Parent for FooParent {}

struct FooChild<T>(T);
impl<T> ParentGat<T> for FooParent {
    type Child = FooChild<T>;
    
    fn make_child() -> Self::Child { ... }
}
#15

Thank you very much, I get this pattern now and it would almost achieve what I need. Unfortunately, to actually call the make_child_general method in some type or function that uses Trait generically, will also need a bound on ParentGat with the appropriate T. In my case, this would include private types , as well, so I can’t put the bound in the public interface. I’ll experiment some more with this pattern, but I have my doubts if it’s going to work.

I assume when ‘the real deal’ (actual GATs/existential types) lands, such issues will disappear.