Сопоставление типов вместо «признака импл» в определении признака

#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'
    }
}
 

Я знаю, что есть несколько (возможных) решений моей конкретной проблемы :

  1. Определите MyMap тип , который реализует Stream , так же, как Then реализует тип Stream .
  2. Оберните вывод fn_to_future в общий тип, параметризованный F и реализующий Fn(Self::Item) -> Ready<T> .
  3. Попробуйте использовать поле в качестве вывода функции признака (т. Е. Обойти impl Trait ограничения).

Решение 1 наверняка сработает, но я чувствую, что это написание большого количества ненужного кода (см. Объем кода, используемого для my_map функции), и в основном будет дублировать Then реализацию.

Решение 2 должно работать, но потребует использования нестабильного, чего я хотел бы избежать, так как нет четкой даты, когда Fn черты станут стабильными (см. проблему отслеживания).

Я действительно не изучал решение 3, но нахожу его очень неэлегантным (и в моем случае может возникнуть много проблем).

Итак, мой вопрос состоит из двух частей:

  1. почему здесь не удается сопоставить типы (я не совсем понимаю, является ли это слабостью компилятора или проблемой надежности), и можем ли мы это исправить?
  2. есть ли какое-либо другое элегантное и эффективное решение (как уже объяснялось, на данный момент у меня есть либо неэлегантные, либо неэффективные решения)?

Спасибо!

Комментарии:

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 функции. Ваше последнее решение-это именно то, что я предполагал во втором предложении моего вопроса.