Weird error with tokio::try_join! and mapped futures

I further modified the example to better show where the problem happens (and where modifications of the code would "fix" the compiler error):

use futures::future::FutureExt;
use std::borrow::Cow;
use std::future::Future;
use std::pin::Pin;

fn join<Fut1, Fut2>(future1: Fut1, future2: Fut2) -> impl Future<Output = ()>
where
    Fut1: Future,
    Fut2: Future,
{
    let future = futures::future::join(future1, future2);
    async move {
        future.await; // remove this line
    }
}

struct S {}

impl S {
    fn foo<'a: 'b, 'b>(
        &'a self,
    ) -> Pin<Box<dyn 'b + Future<Output = Cow<'a, ()>> + Send>>
    {
        Box::pin(async move {
            println!("Executed foo");
            Cow::Owned(())
        })
    }
    fn bar<'a, 'b, 'c>(
        &'a self,
        other: &'b S,
    ) -> Pin<Box<dyn 'c + Future<Output = ()> + Send>> // or remove Send here
    where
        'a: 'c,
        'b: 'c,
    {
        Box::pin(async move {
            join(
                self.foo().map(|x| x),  // or remove .map(…) here
                other.foo().map(|x| x), //    and here
            )
            .await;
        })
    }
}

#[tokio::main]
async fn main() {
    let s1 = S {};
    let s2 = S {};
    s1.bar(&s2).await;
}

(Playground)

Unfortunately, I don't understand why the error happens in the first place.

It seems that higher-ranked trait bounds might be necessary for this, but I would have little to no idea as to where (and how) to specify them properly. E0582 doesn't want go away no matter what.

1 Like

I think the error has to do with the closures not inferring that they should be using HRTBs. Unfortunately you can't really specify HRTBs on closures.

You have to force the inference, but it can be done.

fn higher_ranked_cow_bound<F>(f: F) -> F
where
    F: for<'a> FnOnce(Cow<'a, ()>) -> Cow<'a, ()>,
{
    f
}

// ...

        let id = higher_ranked_cow_bound(|x| x);
        Box::pin(async move {
            join(
                self.foo().map(id), 
                other.foo().map(id),
            )
            .await;
        })
2 Likes

So if I understand it right, the problem is that the compiler doesn't figure out that the two lifetimes must be equal, and I have to manually give a hint?

In my original example (at the top of this thread), I actually operate on an io::Result<Cow<'a, ()> instead of a Cow<'a, ()>. And what I want to achieve in the end isn't a .map(|x| x) but a .map(|x| map_error_type_of_result(x))

I tried to modify your higher_ranked_cow_bound method to fit my project, and I ended up with something like this:

fn id<F, T>(f: F) -> F
where
    F: for<'a> FnOnce(Result<Cow<'a, T>, io::Error>)
               -> Result<Cow<'a, T>, CustomError>,
    T: Clone,
{
    f
}
let (value1, value2) = try_join!(
    self
        .get_value()
        .map(id(|res| map_result_A(res))),
    other
        .get_value()
        .map(id(|res| map_result_B(res))),
)?;

That code above works in my project and will fix the compiler error! :+1:

However, in my real world code, the map_result_… functions (which are actually methods implemented on Result through an extension trait), operate on any Result<T, io::Error>, and not just on Cows. I thus tried to generalize the id function above:

fn id<F, T>(f: F) -> F
where
    F: for<'a> FnOnce(Result<T, io::Error>)
               -> Result<T, CustomError>,
{
    f
}

But then I get (as could have been expected) the same error: implementation of `FnOnce` is not general enough.

I also tried weird stuff like:

fn id<'t, F, T>(f: F) -> F
where
    F: for<'a> FnOnce(Result<T, io::Error>)
               -> Result<T, CustomError>,
    T: 't,
{
    f
}

or

fn id<'t, F, T1, T2>(f: F) -> F
where
    F: for<'a> FnOnce(Result<T1, io::Error>)
               -> Result<T2, CustomError>,
    T1: 't,
    T2: 't,
{
    f
}

Of course, the for<'a> gets useless here, so no wonder I also get the error: implementation of `FnOnce` is not general enough again :sob:.

I guess I would need something like the following, which isn't valid Rust syntax:

fn id<F, T1<'t1>, T2<'t2>>(f: F) -> F
where
    F: for<'a> FnOnce(Result<T1<'a>, io::Error>)
               -> Result<T2<'a>, CustomError>,
{
    f
}

Or, I would need the compiler to correctly infer the lifetimes by itself. :pleading_face:

I think my use case isn't so uncommon, as mapping an error (or Result) type of a future and joining two of such futures should be something that happens in other projects too (unless you never map errors and only pass them on).

Not quite! The problem is that SomeTrait<'a> and SomeTrait<'b> are two different traits. In our case, the closure is inferred to implement only SomeTrait<'a> for one specific lifetime 'a, and not implement the trait for any other lifetime. However, what we want is that it implements all the infinitely many traits SomeTrait<'a>, SomeTrait<'b>, ... SomeTrait<'static>, and the way this is written is as for<'a> SomeTrait<'a>, where the for<'a> means "for all lifetimes 'a".

In the above I use SomeTrait<'a> has a shorthand for FnOnce(Result<Cow<'a, T>, io::Error>) -> Result<Cow<'a, T>, CustomError>. Note that even when it implements only SomeTrait<'a> for one specific lifetime 'a, the two lifetimes in the FnOnce are still the same lifetime.

So how about:

fn id<F>(f: F) -> F
where
    F: for<T> FnOnce(Result<T, io::Error>)
              -> Result<T, CustomError>,
{
    f
}

Unfortunately, that's not valid Rust syntax either :frowning_face:

(My problem here is, that I have several occurrences where I want to map errors, and having to define an id function with yet another long type signature each time would really mess up all my code, as I do a lot of error mapping for a clean interface).

Shouldn't the compiler be able to figure out by itself that when I map the error, the lifetimes of the Ok values aren't touched at all? It looks like it's impossible to specify it in a general way (e.g. with for<T> …) and also impossible to infer it (at least with the current compiler).

I'm asking myself: What am I supposed to do? Not map errors at all? Not execute futures concurrently? Or fill my code with id functions having verbose type signatures (which each map a value to itself) just to prove to the compiler that everything is okay? :weary:

P.S.: An alternative would be to only use 'static types (as the error didn't happen in that case, see Playground with Cows removed), but that doesn't sound like a good alternative as well. (For example, I introduced Cows in my code to avoid copying values unnecessarily.)


Note that T in my examples (or Cow) isn't a trait, but a type (or rather a type constructor in the latter case?).

What I need (I think) is that I want the compiler to understand that it works on SomeType<'a>, SomeType<'b>, …, but also AnotherType<'a>, AnotherType<'b>, etc…, i.e. where a closure C fulfills the bound C: FnOnce(Result<WhicheverType<'a>, io::Error>) -> Result<WhicheverType<'a>, CustomError>.

But I'm not sure. I'm actually confused due to the complexity of this issue. :woozy_face: All I would like is mapping error types and executing futures concurrently. :sweat_smile:

I just found an old issue Higher-ranked types in trait bounds #1481 proposing something like for<T>. Maybe that is related to the problem?

I still feel like the compiler should be able to solve this automatically, but maybe I understand too little about the implications involved with that.

Note that my use-case isn't all theoretic, but something I'd expect to be needed in practice in many cases: mapping error types with closures + using non-'static types. (Not sure how the futures play into this problem.)

I finally found a solution for my particular problem, that I would like to present here. :smiley:

In fact, the type of the Ok value doesn't change at all (in my particular use case). So what I did was to create an extension trait for the Future trait:

use std::future::Future;
use futures::future::FutureExt as _;

pub trait FutureMapResErrExt<M, O>: Future + Sized {
    type Ok;
    type Err;
    type FutureMapResErr<E2>: Future<Output = Result<O, E2>>;
    fn map_res_err<E2>(self, mapper: M) -> Self::FutureMapResErr<E2>
    where
        M: FnOnce(Self::Err) -> E2;
}

impl<F, O, E, M> FutureMapResErrExt<M, O> for F
where
    F: Future<Output = Result<O, E>>,
{
    type Ok = O;
    type Err = E;
    type FutureMapResErr<E2> = impl Future<Output = Result<O, E2>>;
    fn map_res_err<E2>(self, mapper: M) -> Self::FutureMapResErr<E2>
    where
        M: FnOnce(Self::Err) -> E2,
    {
        self.map(|result| result.map_err(mapper))
    }
}

This extension trait extends all Future<Output = Result<O, E>> by a method map_res_err, which gets a closure as argument that maps the error inside the Result of the Output of the future from type E to some other error type E2.

The resulting future has type FutureMapResErr<E2> which is bound to be Future<Output = Result<O, E2>>, where O is the Ok type of the Result of the Output of the original future, i.e. the compiler knows that the Ok type (including any lifetimes) isn't modified by the mapping.

I can then use the new method as follows:

let (value1, value2) = tokio::try_join!(
    self.get_value(index).map_res_err(|x| mapperA(x)),
    other.get_value(index).map_res_err(|x| mapperB(x)),
)?;

I modified the original Playground to use the map_res_err method:

Modified Playground (uses nightly Rust).

I'd appreciate any feedback on my solution. Is it a reasonable fix/workaround for this problem? Have I implemented the extension trait (FutureMapResErrExt) correctly? And what do you think about whether the compiler should have figured this out by itself (and can someone explain why (not))?

This was a really tough problem for me. :crazy_face: Thanks for all the guidance, without your help I'd have been totally clueless! (And maybe there's still something wrong with my solution? Anyway, for now it seems to work in my code…)


Update:

Apparently, I can get rid of the two associated types Ok and Err:

use std::future::Future;
use futures::future::FutureExt as _;

pub trait FutureMapResErrExt<M, O, E>: Future + Sized {
    type FutureMapResErr<E2>: Future<Output = Result<O, E2>>;
    fn map_res_err<E2>(self, mapper: M) -> Self::FutureMapResErr<E2>
    where
        M: FnOnce(E) -> E2;
}

impl<F, O, E, M> FutureMapResErrExt<M, O, E> for F
where
    F: Future<Output = Result<O, E>>,
{
    type FutureMapResErr<E2> = impl Future<Output = Result<O, E2>>;
    fn map_res_err<E2>(self, mapper: M) -> Self::FutureMapResErr<E2>
    where
        M: FnOnce(E) -> E2,
    {
        self.map(|result| result.map_err(mapper))
    }
}

Or, alternatively, I can get rid of the type parameters O and E in the trait definition if I use associated types again (and then use Self::Ok instead of O and Self::Err instead of E). Not sure what's the better solution.

For some reason, it seems like I can't get rid of the parameter M to FutureMapResErrExt.

1 Like

Different closures (Ms) become part of your returned future in your implementation, so you can have different FutureMapResErr<E2>s for the same E2. This isn't allowed within a single implementation (like when M is a parameter on the fn), but it's okay across multiple implementations if M is the same within each single implementation (like when M is a parameter on the impl/trait).

Or when M is an associated type or maybe GAT... but that would limit you to one specific closure for each concrete future or such :sweat_smile:. (And it's probably not possible to end up with a defining case for TAIT via a call site?)

Not sure if I fully understand you, but I gave it another try to pass the type of M as associated type:

use std::future::Future;
use futures::future::FutureExt as _;

pub trait FutureMapResErrExt<O, E1, E2>: Future + Sized {
    type Map: FnOnce(E1) -> E2;
    type FutureMapResErr: Future<Output = Result<O, E2>>;
    fn map_res_err(self, mapper: Self::Map) -> Self::FutureMapResErr;
}

impl<F, O, E1, E2, M> FutureMapResErrExt<O, E1, E2> for F
where
    F: Future<Output = Result<O, E1>>,
    M: FnOnce(E1) -> E2,
{
    type Map = M;
    type FutureMapResErr = impl Future<Output = Result<O, E2>>;
    fn map_res_err(self, mapper: M) -> Self::FutureMapResErr {
        self.map(|result| result.map_err(mapper))
    }
}

That gives me:

error[E0207]: the type parameter `M` is not constrained by the impl trait, self type, or predicates
  --> src/future_ext.rs:10:20
   |
10 | impl<F, O, E1, E2, M> FutureMapResErrExt<O, E1, E2> for F
   |                    ^ unconstrained type parameter

As I can't omit M in the type parameter list of the trait, I decided to go for this syntax that seems shortest:

use std::future::Future;
use futures::future::FutureExt as _;

pub trait FutureMapResErrExt<O, E1, E2, M>: Future + Sized {
    type FutureMapResErr: Future<Output = Result<O, E2>>;
    fn map_res_err(self, mapper: M) -> Self::FutureMapResErr;
}

impl<F, O, E1, E2, M> FutureMapResErrExt<O, E1, E2, M> for F
where
    F: Future<Output = Result<O, E1>>,
    M: FnOnce(E1) -> E2,
{
    type FutureMapResErr = impl Future<Output = Result<O, E2>>;
    fn map_res_err(self, mapper: M) -> Self::FutureMapResErr {
        self.map(|result| result.map_err(mapper))
    }
}

That code works fine for me. Note that in the last example, I don't need GATs anymore (but still use TAITs).

I assume it's also possible to implement this without TAITs (type-alias-impl-trait), but it would make me have to define my own future type manually – and cause me some extra headache :sweat_smile:

Some questions remain, though. First of all, while this "solution" works for me, I don't think it is a generic solution for everyone. My solution is specific to mapping the error part of Result types. Any types other than Results aren't covered by my approach (and would require their own extension trait). It might thus be questionable to extend the futures crate with such an extension trait.

On the other hand, what I do doesn't seem so exotic:

Isn't that something other people might want to do as well? Anyone who tries this would likely run into the same problems as I did? Or was it something specific I did, which got me into this trouble, which could have been avoided?

I'm worried other people can run into this problem and will get:

error: implementation of `FnOnce` is not general enough

@alice came across this compiler error in another context where it was also highly confusing for the user:

I still don't understand the whole problem well enough to say whether

  • the compiler should simply give a better error message,
  • the type inference mechanism of the compiler should be improved to not throw this error in the first place (such that it's not required to create an extension trait like I proposed or use identity funcitons to force the inference like @quinedot demonstrated), and whether
  • the type inference mechanism could be improved at all (or if there are some type-theoretic limitations on this).

I would like to point out that my solution with impl<F, O, E1, E2, M> FutureMapResErrExt<O, E1, E2, M> for F is far from being simple to write down for programmers who might not expect to run into this sort of problem. It cost me several days to understand all this. I'm happy I spent the time on it, as I – once more – learned a lot more about Rust, but other people might be less happy about running into these problems.

I haven’t been following this thread so you may already know this or there may be other reasons to implement it, but your FutureMapResErrExt extension looks a lot like TryFutureExt::map_err from the futures crate at a quick glance

1 Like

I just figured out that if I hadn't used the futures::future::FutureExt::map method but did it manually with async move and match, the problem wouldn't have occurred, see Playground. So I was thinking "too functional" here. But other people might as well prefer the functional way of expressing this and thus run into the problem.

Oh, this actually solves my problem out of the box! Thanks a lot of that hint. There is no problem (for me) with using that. Here's a link to TryFutureExt::map_err. I might use that instead of my own extension trait.

So if I had either used a manual match or if I used the Result-specific extension trait, the problem wouldn't have occurred. Going the middle course (using map but not map_err) got me into trouble here. I still believe other people might run into that issue (perhaps in different contexts, other than involving Futures with Results).

Having M as both a type parameter and an associated type in the impl is like this function:

fn f<T: Display>(t: T) -> impl Display {
    t
}

Where you're supposed to have one implementation-defined return type as an output, but you based it on an input parameter that is generic, and that the implementation doesn't get to choose. (It's pretty much exactly like this in fact -- the Output of the Fn traits are associated types.)

Your associated TAIT is capturing M, so it's the same sort of issue. Hopefully that clears up what I meant.


I wasn't serious about getting rid of M as a trait parameter, the associated type comment was just a technical side note / toying with it for fun (I did get a ICE out of it). Though you could remove it with some sort of type erasure (fn pointer or dyn FnOnce). I don't think that's really a goal on its own though.

1 Like

But can't each implementation of the trait specify their own type for each associated type? I.e. each typle of types <F, O, E1, E2, M> results in a different FutureMapResErr, for example. Therefor, I don't see (yet) why I can't pass the M the same way I can pass the other types.

But it's really not that important, I think I'll need more practice with type parameters and associated types in future. AFAIK, associated types can't achieve anything that type parameters couldn't achieve (except better readable code where the type isn't really "free" to choose).

But thanks for trying to explain.

Ah, okay :grinning_face_with_smiling_eyes:

Yeah, I wouldn't want type erasure here.

In the process of writing this up, I became uncertain if we're talking past each other or not. It mainly depends on whether or not you're taking GATs into account or not. With enough use of GATs and generic methods, you can get rid of the trait parameters. (I hit the ICE and bailed before getting there on my first go, but succeeded this time -- link at the end.)

But... since I'm uncertain if that's what you meant or not, let's ignore GATs for the moment and concentrate mostly on stable. I'm going to weave TAIT in and out some (and we'll see how it both obscures and enables things), and then come back to GATs at the end.

(Side note: When the playground links below have stable in the URL, the run button is still coming up "Nightly" for me; I'm not sure why. In case it's the Playground and not my browser, I'll just note that any playgrounds linked below that do not use features work on stable.)


Okay, let's start with a simplified example using TAIT.

pub trait ResExt<D> {
    type Output: Debug;
    fn dmap(self, debug: D) -> Self::Output where D: Debug;
}

impl<D, O, E> ResExt<D> for Result<O, E> {
    type Output = impl Debug;
    fn dmap(self, debug: D) -> Self::Output where D: Debug {
        // Wrapper<Result<D, E>>
        Wrapper(self.map(|_| debug))
    }
}

fn main() {
    let res: Result<i32, ()> = Ok(42);
    let one = res.dmap("foo".to_owned());
    let two = res.dmap(vec![13]);
    
    println!("{:?}", one); // Wrapper<Result<String, ()>>
    println!("{:?}", two); // Wrapper<Result<Vec<i32>, ()>>
}

This all works. Two implementations for Result<i32, ()> get instantiated: an implementation of ResExt<String> (with Output = Wrapper<Result<String, ()>>) and an implementation of ResExt<Vec<i32>> (with Output = Wrapper<Result<Vec<i32>, ()>>). Note how the TAIT hid the capture of the type parameter D into the associated type Output.

Let's get rid of the TAIT so less things are hidden. (This demonstrates some utility of TAIT: this removal isn't always possible if the types are opaque, and even though they are not in this case, I had to change the privacy of Wrapper and move the Debug bound to the implementation and not just the method.)

Can we get rid of the D parameter on stable? Let's try moving D to the method. Spoilers, it doesn't work. Well, the trait definition is fine, but there's no way to make Output depend on D, because D isn't in scope for Output anymore. This is one of the main things I was getting at -- it's just like your M in the previous comments, though TAIT makes the dependency harder to see.

And it would be problematic if the Output could depend on D, because users of dmap can supply any D they want (if it meets the Debug bound) -- but we now only have one implementation of ResExt for any given type Result<X, Y>. Coherence demands we find the singular implementation, which contains a dmap function that returns a single, concrete type. Because there can only be one implementation per concrete Result<X, Y>, there can only be one Output type.

If you try with TAIT, the error is more obscure. But the "parameter list for impl Trait" is limited precisely to avoid problems like the coherence ones here.


Alright, try two. How about if we make D another associated type instead? Because if we use an associated type, Output can refer to it once again.

Hey, it works! Err, sort of. We still only have one implementation (per concrete Result type), so there can only be one associated type. So if we want to use a different D, well, we can't. dmap is no longer generic for a concrete Result type -- it can't be generic over multiple implementations, and it's not a generic method either.

In your async case, if every Future you want to implement this trait only needs to accept a single closure, this still might work. But if there's a concrete future type that needs to accept more than one closure, it won't. (This is another case where I'm not sure if we're talking past each other.)

TAIT doesn't change this (and my guess was right, your defining use has to be in the impl).


I've been ignoring GATs until now. Are they up to the task? Yes, GATs can do it. We make the method itself generic on the input, and then we weave the generic input into the now-generic Output.

Interesting side note:

I had to move the Debug bound off of the Output type and on to the dmap method in this version, because Wrapper<Result<In, E>> doesn't always implement Debug. Even though we only return Debug implementers, you can do things like this:

fn f() {
    struct NotDebug;
    let wrapper: <Result<i32, ()> as ResExt>::Output<NotDebug> = Ok(NotDebug);
    println!("{:?}", wrapper); // if this compiles, someone's lying
}

Well, maybe that's not too interesting on it's own... but if we add back TAIT again, we can move the bound back to Output. Uh oh, is this a problem? It is not, because we can't construct the opaque type. Turns out this is very related to why I had to move the Debug bound on the implementation when I removed TAIT the first time, above.


Alright, finally, let's apply this to your code. I'll start with the moe version.

First, we only need one implementation per Result<O, E>, so we can demote those to (plain) associated types.

Next, we move M to the method instead of the trait. This version errors, and I think it's worth pausing to think it through. We can't get rid of TAIT because of all the opaque types, but there's that pesky parameter list error again -- this is just like our original attempt above to move D from the trait to the method! It's a little more confusing because FutureMapResErr is already a GAT. But we still must explicitly weave the input (M) into the GAT. That will put M in the parameter list for the GAT. (This is probably a suggestion the compiler will make, once GAT + TAIT support is more fleshed out.)

Once we do that, it works. Lots of TAIT and GAT, but no dyn and no type parameters on the trait.

1 Like

I think I can follow now. Using TAITs, I can make the impl Future<Output = Result<O, E2>> differ for each M:

type FutureMapResErr<T> = impl Future<Output = Result<O, E2>>;
fn map_res_err<M>(self, mapper: M) -> Self::FutureMapResErr<M>
where
    M: FnOnce(E1) -> E2,
{
    /* … */
}

(Playground with only M removed as type parameter of the extension trait)

But what I don't understand yet is the following:

It seems I thought that type Ty = impl … doesn't imply there is only one type Ty, but one type Ty for each implementation, i.e. one particular type for each type tuple <F, O, E1, E2> (as specified after impl). That should be correct. But then, I get: error[E0207]: the type parameter M is not constrained by the impl trait, self type, or predicates. (Playground)

So I would say this is kind of a syntax restriction that it's forbidden to add type parameters which apparently aren't used? (See rustc --explain E0207) Except in this case, the type parameter M would be used because it should serve as a quantifier for the associated type (TAIT), so it isn't useless (but the compiler thinks it is?).


Update:
Maybe the quantification of impl Trait must always be explicit, either through a type parameter (using GATs) or by explicitly naming a type parameter on the right-hand side.

Yes, well... I would say, using GATs. Or GATs and TAITs; if you can't name your outputs, you need both.

I've hit this error myself and thought "hmmm really?", but have never really followed up on it. You can read RFC 447 for the motivations; the case of an unconstrained associated type is explicitly called out as a drawback. But I can see how it would be problematic: under RFC 447, if we have a concrete type that implements a concrete trait, there is only one possible implementation, and thus all associated types and other non-parameterized trait items are also concrete. Allowing this case would break that tautology, which is used a lot.

trait Tr<X> { type D: Default; }
fn _f<T: Tr<()>>(_: T) {
    // If you can call this function, `T::D` is a concrete type
    let _: T::D = Default::default();
    // More generally,
    let _: <SomeType<A, B> as SomeTrait<C, D, E>>::F = ...;
    // If the type and all its inputs are known,
    // and the trait and all its inputs are known,
    // then then impl and all its inputs are also known,
    // and thus its items (like associated types) are also known
}

Anyway, yes, there is exactly one (plain) associated type per implementation. That's true independent of whether the type uses TAIT or not.

In a more general sense, for any $Thing, there's exactly one definition for every set of concrete input parameters that satisfy their bounds. You can look at (plain) associated types as:

  • Having no input parameters, within the context of a concrete implementation
    • And thus there's only one in this context
  • Having the same input parameters as the implementation, within a broader context
    • E.g. <Vec<String> as Deref>::Target

If I understand what you mean by quantification -- ways to make multiple? -- then there must be a parameter somewhere, and that parameter must be "in scope" to be part of the definiton (be it explicitly on the right, or implicitly in the defining use). As I understand it, in fact, impl Trait captures all parameters in scope (which is of practical importance if the parameters happen to carry lifetimes).

Outside a trait, that can be a parameter on a generic function:

  • fn foo<X>() -> impl Trait ...

Within a trait, that parameter might be:

  • On a GAT: type Gat<X> = impl Trait ...
  • On the trait itself (when impl Trait is on either a GAT or a plain associated type)
  • But return-position impl Trait is still not allowed...

So if there's going to be more than one definition per implementation, that means a GAT must have supplied the parameter. And to emulate return-position impl Trait, if you want the function parameters to be in scope, you have to weave them through by adding parameters to a GAT as well.

  • :x: fn method<T>(&self) -> impl Trait;
  • :white_check_mark: fn method<T>(&self) -> Self::Gat<T>; type Gat<T>: impl Trait;

(I suspect there will be sugar for this some day, but who knows when.)

1 Like

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.