#rust
Вопрос:
В эти дни я играю с асинхронной экосистемой Rust и, в частности, потоками, и я хочу реализовать эквивалент map()
метода (from StreamExt
) с некоторыми особенностями, которые здесь не интересны. И поскольку мне не разрешается писать impl Stream
в качестве возвращаемого типа признака расширения, который я хочу создать, я чувствую себя довольно ограниченным в выборе API, которые я могу определить.
Мой код выглядит так (я упростил fn_to_future
функцию):
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'
}
}
Я знаю, что есть несколько (возможных) решений моей конкретной проблемы :
- Определите
MyMap
тип , который реализуетStream
, так же, какThen
реализует типStream
. - Оберните вывод
fn_to_future
в общий тип, параметризованныйF
и реализующийFn(Self::Item) -> Ready<T>
. - Попробуйте использовать поле в качестве вывода функции признака (т. Е. Обойти
impl Trait
ограничения).
Решение 1 наверняка сработает, но я чувствую, что это написание большого количества ненужного кода (см. Объем кода, используемого для my_map
функции), и в основном будет дублировать Then
реализацию.
Решение 2 должно работать, но потребует использования нестабильного, чего я хотел бы избежать, так как нет четкой даты, когда Fn
черты станут стабильными (см. проблему отслеживания).
Я действительно не изучал решение 3, но нахожу его очень неэлегантным (и в моем случае может возникнуть много проблем).
Итак, мой вопрос состоит из двух частей:
- почему здесь не удается сопоставить типы (я не совсем понимаю, является ли это слабостью компилятора или проблемой надежности), и можем ли мы это исправить?
- есть ли какое-либо другое элегантное и эффективное решение (как уже объяснялось, на данный момент у меня есть либо неэлегантные, либо неэффективные решения)?
Спасибо!
Комментарии:
1. Хотя решение 1, безусловно, имеет некоторые накладные расходы, я не думаю, что это так уж плохо. Вы должны быть в состоянии реализовать это примерно в 50 строках кода, что мне кажется разумным.
2. Решение № 3 будет выглядеть так: play.rust-lang.org/…
3. @SvenMarnach Это то, что я в итоге сделал, и это работает как заклинание.
Ответ №1:
Проблема заключается в том, что функция объявляется следующим образом:
fn my_map_in_trait<F, T, G>(self, f: F) -> Then<Self, Ready<T>, G>
Означает, что G
это определено вызывающим абонентом. Это определенно не входит в наши намерения. Поскольку вы не хотите определять новый Stream
тип, вам нужен именованный тип вместо G
. Вот несколько вариантов для вас:
1. Вставьте в коробку Fn
Реализация выглядит так:
fn my_map_in_trait<'a, F, T>(self, f: F) -> Then<Self, Ready<T>, Box<dyn Fn(Self::Item) -> Ready<T> 'a>>
where
F: Fn(Self::Item) -> T,
F: 'a,
T: 'a,
Self::Item: 'a,
{
self.then(Box::new(fn_to_future(f)))
}
Это должно быть лучше, чем упаковывать Stream
реализацию, потому что в таблице vtable потребуется создать меньше функций.
2. экзистенциальные типы / TAIT (признак псевдонима типа)
Решение только на ночь, но не имеет никаких накладных расходов: игровая площадка
#![feature(type_alias_impl_trait)]
use futures::{future::Ready, stream::Then, Stream, StreamExt};
pub fn fn_to_future<S: Stream, F, T>(f: F) -> Fun<S, F, T>
where
F: Fn(S::Item) -> T,
{
move |input| {
let out = f(input);
futures::future::ready(out)
}
}
type Fun<S: Stream, F, Output> = impl Fn(S::Item) -> Ready<Output>;
pub trait MyMapStreamExt: Stream Sized {
fn my_map_in_trait<'a, F, T>(self, f: F) -> Then<Self, Ready<T>, Fun<Self, F, T>>
where
F: Fn(Self::Item) -> T,
{
self.then(fn_to_future:<Self, _, _>(f))
}
}
3. Распакованные крышки
Еще одно единственное ночное решение. Это решение не использует impl Trait
, поэтому нет непрозрачных типов: игровая площадка
#![feature(fn_traits)]
#![feature(unboxed_closures)]
use futures::{future::Ready, stream::Then, Stream, StreamExt};
pub struct ToFuture<F>(F);
impl<F, I, O> FnOnce<(I,)> for ToFuture<F>
where
F: FnOnce(I) -> O,
{
type Output = Ready<O>;
extern "rust-call" fn call_once(self, (i,): (I,)) -> Ready<O> {
futures::future::ready((self.0)(i))
}
}
impl<F, I, O> FnMut<(I,)> for ToFuture<F>
where
F: FnMut(I) -> O,
{
extern "rust-call" fn call_mut(amp;mut self, (i,): (I,)) -> Ready<O> {
futures::future::ready((self.0)(i))
}
}
pub fn fn_to_future<F>(f: F) -> ToFuture<F> {
ToFuture(f)
}
pub trait MyMapStreamExt: Stream Sized {
fn my_map_in_trait<'a, F, T>(self, f: F) -> Then<Self, Ready<T>, ToFuture<F>>
where
F: Fn(Self::Item) -> T,
{
self.then(fn_to_future(f))
}
}
Комментарии:
1. Спасибо @Deadbeef за подробный ответ: теперь я лучше понимаю ошибку. Я не думал о вашем 1-м решении и не знал о
type_alias_impl_trait
функции. Ваше последнее решение-это именно то, что я предполагал во втором предложении моего вопроса.