How can I name the type returned by a function for use in a struct field?

Let's say I have this trait for something that can be called serially to do async work:

trait Frobnicator {
    fn frobnicate(&mut self) -> impl Future<Output = ()>;
}

This is not dyn compatible, because it returns an arbitrary future type. I can make it dyn compatible by having it return Box<impl Future<Output = ()>, but that throws away an optimization opportunity—because it's only called serially, a dyn-compatible implementation could provide internal storage for the future that is reused with each call. Something like this:

trait ErasedFrobnicator {
    fn frobnicate_erased(&mut self) -> Pin<&mut impl Future<Output = ()>>;
}

Here's the problem though: in order to provide that storage, I need to name the future's type. In an unholy mix of Rust and C++ I could do this using decltype:

struct FrobnicatorView<F: Frobnicator>(
    F, MaybeUninit<decltype(declval<F>().frobnicate())>);

impl ErasedFrobnicator for FrobnicatorView {
    // ...
}

But of course decltype doesn't exist. So is there any trick to refer to the name of the future type returned by <F as Frobnicator>::frobnicate in order to provide a field like this? I could also have Frobnicator provide an associated type for this, but that makes implementing Frobnicator inconvenient because implementors can no longer use async fn (otherwise they would have the same naming problem over again) .

1 Like

Type alias impl trait or one of its variants help here?

1 Like

I don't think so; it sounds like that is for hiding one particular implementation of a trait, not for accepting a generic implementation but being able to know the size of the result of one of its methods.

#![feature(type_alias_impl_trait)]

trait Frobnicator {
    fn frobnicate(&mut self) -> impl Future<Output = ()>;
}

type Opaque<'a, T: Frobnicator> = impl Future<Output = ()>;

#[define_opaque(Opaque)]
fn frobnicate_helper<T: Frobnicator>(x: &mut T) -> Opaque<'_, T> {
    x.frobnicate()
}

Does this work for you?

Another alternative:

#![feature(impl_trait_in_assoc_type)]

trait Frobnicator {
    type Fut: Future<Output = ()>;
    fn frobnicate(&mut self) -> Self::Fut;
}

struct Thing;
impl Frobnicator for Thing {
    type Fut = impl Future<Output = ()>;
    fn frobnicate(&mut self) -> Self::Fut {
        async {}
    }
}

Thanks, I think that's the feature I was looking for. In order to do anything useful it looks like I also need for the type to be generic over a lifetime. (I thought this was unstable but I didn't need to opt into any features…)

#![feature(impl_trait_in_assoc_type)]

trait Frobnicator {
    type Fut<'a>: Future<Output = ()> where Self: 'a;
    fn frobnicate(&mut self) -> Self::Fut<'_>;
}

struct Thing(i32);

impl Frobnicator for Thing {
    type Fut<'a> = impl Future<Output = ()> where Self: 'a;
    
    fn frobnicate(&mut self) -> Self::Fut<'_> {
        async { self.0 = 0; }
    }
}