Rust: Generi c возвращает типы в чертах для реализаций, которые возвращают необъективные черты - PullRequest
0 голосов
/ 11 апреля 2020

Я новичок в Rust, и я хотел выучить язык и лучше понять, реализовав несколько небольших проектов. Моей первой попыткой было проанализировать JSON данных, полученных от MQTT Broker. Мне было очень приятно, как легко было выполнить sh с помощью tornado и serde. Однако быстро возникла модель, которую я хотел (в идеале) извлечь в черту.

let person_stream = sender.subscribe().filter_map(|data| {
    if let Ok(value) = data {
        return serde_json::from_slice::<Person>(&value).ok();
    }
    None
});

, где sender обычно представляет собой tokio::sync::*::Sender, а Person реализует черту serde::de::DeserializeOwned.

Таким образом, цель состояла в том, чтобы реализовать что-то, что берет tokio::stream::StreamExt<Item = Vec<u8>> и transform это в другое StreamExt, где связанный тип Item будет реализовывать DeserializOwned.

Это заняло мне достаточно времени, чтобы что-то выяснить (так как я изначально хотел использовать Trait или функцию с обобщениями), и я придумал

fn transform<T, U, V>(stream: U) -> impl StreamExt<Item = T>
where
    T: serde::de::DeserializeOwned,
    U: StreamExt<Item = Result<Vec<u8>, V>>,
{
    stream.filter_map(|data| {
        if let Ok(value) = data {
            return serde_json::from_slice::<T>(&value).ok();
        }
        None
    })
}

В то время как это работает, я изначально хотел использовать Trait вроде

trait Transform<T>
{
    fn transform(self) -> T;
}

или реализовать Into, что на самом деле то же самое, что я мог бы реализовать для StreamExt<Item = Vec<u8>>. Так как impl Trait возврат не доступен для черт, это был единственный вариант, который у меня был. Однако я сталкиваюсь с парой проблем, связанных с реализацией этого.

  1. Использование tokio::stream::filter_map::FilterMap<_,_> для T (что является типом возврата filter_map()) невозможно, так как модуль filter_map is private.
  2. Использование Box<dyn StreamExt> также невозможно, так как StreamExt возвращает Self в паре функций. Хотя я не хотел, чтобы в первую очередь были использованы служебные данные Heap;)

Поэтому мой вопрос: могу ли я здесь что-нибудь сделать, чтобы получить сахар syntacti c реализации Trait, учитывая факт, что тип возвращаемого значения filter_map() равен private, а StreamExt не является объектно-безопасным? Было бы здорово просто использовать

let person_stream = receiver.transform();

или into(). Очевидно, у меня есть работающая реализация, так что это не очень важный вопрос для меня. Но, как я сказал в начале, я хотел получить более глубокое и лучшее понимание Rust.

Я заметил, что есть ящик tokio-serde, но на первый взгляд он имеет дело только с данными в кадрах , поэтому я не углубился в это.

PS: Я также столкнулся с проблемой с бесплатной функцией transform, которую я реализовал, когда вывод типа завершился неудачей. Например, при передаче нового потока функции, подобной

async fn debug_sink<T>(mut receiver: T)
where
    T: StreamExt + Unpin,
    T::Item: std::fmt::Debug,
{
    while let Some(item) = receiver.next().await {
        println!("Item: {:#?}", item);
    }
}

В этом случае он явно не может вывести T в transfer в отличие от приемников, таких как

async fn person_sink(mut receiver: impl StreamExt<Item = Person> + Unpin) {
    while let Some(person) = receiver.next().await {
        println!("Person: {:#?}", person);
    }
}

Однако я не хотел аннотировать все параметры типа, только тот, который не мог быть выведен. С помощью проб и ошибок я обнаружил, что могу использовать

transform::<Person, _, _>(stream);

, о которых я совершенно не знал. Я не мог найти это в документации, хотя. Это какая-то скрытая функция, или я просто не могу правильно rtfm? :)

1 Ответ

1 голос
/ 11 апреля 2020

Я думаю, что то, что вам нужно, доступно в Nightly под функцией type_alias_impl_trait. По сути, это позволяет вам писать связанный тип в признаке, а затем в реализации вместо написания именованного типа мы используем ie синтаксис impl BaseTrait.

Мне лень писать ваш код с type_alias_impl_trait (и вы не предоставили скомпилированный фрагмент), но здесь это рабочий пример ( детская площадка ):

#![feature(type_alias_impl_trait)]

use std::fmt::Debug;

trait Foo {
    type Output: Debug;

    fn do_something() -> Self::Output;
}

impl Foo for () {
    type Output = impl Debug;

    fn do_something() -> Self::Output {
        "hello!"
    }
}

Обратите внимание, как на самом деле ()::do_something возвращает &'static str, но этот тип никогда не упоминается.

...