These days, I am playing with Rust's async ecosystem, and Streams in particular, and I want to implement an equivalent of map() method (from StreamExt) with some specificities that are not interesting here. And because I am not allowed to write impl Stream as the return type of the extension trait I want to create, I feel quite limited in the choice of APIs I can define.
My code looks like that (I simplified the fn_to_future function):
use futures::{future::Ready, stream::Then, Stream, StreamExt};
pub fn fn_to_future<F, I, T>(f: F) -> impl Fn(I) -> Ready<T>
where
F: Fn(I) -> T,
{
// this is a reduced example
move |input| {
let out = f(input);
futures::future::ready(out)
}
}
// This is fine: this is a function, so we can use `impl`
// Also, it hides the actual type of the returned stream and
// allows for a change in the implementation without breaking the API.
pub fn my_map<S, F, T>(stream: S, f: F) -> impl Stream<Item = T>
where
S: Stream,
F: Fn(S::Item) -> T,
{
stream.then(fn_to_future(f))
}
// This is the API I would like to offer to users, as it is
// easier to compose with existing functions
pub trait MyMapStreamExt: Stream + Sized {
fn my_map_in_trait<F, T, G>(self, f: F) -> Then<Self, Ready<T>, G>
// Cannot use 'impl Stream' here as we are in a trait
// So, we try to be explicit about the return type
where
F: Fn(Self::Item) -> T,
G: Fn(Self::Item) -> Ready<T>,
{
self.then(fn_to_future(f)) // the compiler complains here
// as the type parameter 'G' cannot match the opaque type
// returned by 'fn_to_future'
}
}
I know that there are a few (possible) solutions to my particular problem :
- Define a
MyMaptype that implementsStream, just as theThentype implementsStream. - Wrap the output of
fn_to_futurein a generic type parametrised byFand which implementsFn(Self::Item) -> Ready<T>. - Try using a Box as the output of the trait function (i.e. circumvent the
impl Traitlimitations).
Solution 1 will work for sure, but I feel like this is writing a lot of unnecessary code (cf. the amount of code used for the my_map function), and would mostly duplicate the Then implementation.
Solution 2 should work, but would require using unstable, which I would like to avoid, as there is no clear date of when the Fn traits will become stable (cf. the tracking issue).
I haven't really looked into Solution 3, but I find it very inelegant (and might cause many problems in my case).
So, my question is two-folds:
- why does the type matching fail here (I don't quite understand if this is a weakness of the compiler or a soundness issue), and can we fix it?
- is there any other elegant and efficient solution (as explained, I have either inelegant or inefficient solutions at the moment)?
Thanks!