попытка использовать dyn AsyncReadExt, ошибка «признак не может быть преобразован в объект»

#rust #rust-cargo #rust-tokio

#Ржавчина #rust-груз #rust-tokio

Вопрос:

Я работаю над сетевой службой, которая предназначена для работы либо с TcpStream, либо с stdin / stdout. Я получаю ошибку компиляции: the trait tokio::io::util::async_read_ext::AsyncReadExt cannot be made into an object . В настоящее время мой обходной путь заключается в использовании перечисления оболочки:

 enum ClientReader {
    Stream(OwnedReadHalf),
    Stdin(Stdin),
}

enum ClientWriter {
    Stream(OwnedWriteHalf),
    Stdout(Stdout),
}
  

Для этого требуются match блоки повсюду, что кажется неэлегантным.

Я создал упрощенный проект для исправления проблемы:

Cargo.toml

 [package]
name = "demo"
version = "0.1.0"
authors = ["test"]
edition = "2018"

[dependencies]
tokio = { version = "0.2", features = ["full"] }
  

src/main.rs

 use tokio::io::AsyncReadExt;

struct Test {
  test: Box<dyn AsyncReadExt>,
}

fn main () {}
  

Это приводит к аналогичной ошибке:

 error[E0038]: the trait `tokio::io::AsyncReadExt` cannot be made into an object
    --> src/main.rs:4:3
     |
4    |   test: Box<dyn AsyncReadExt>,
     |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `tokio::io::AsyncReadExt` cannot be made into an object
     |
    ::: /home/???/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-0.2.22/src/io/util/async_read_ext.rs:162:12
     |
162  |         fn read<'a>(amp;'a mut self, buf: amp;'a mut [u8]) -> Read<'a, Self>
     |            ---- the trait cannot be made into an object because method `read` references the `Self` type in its return type
...
280  |         fn read_exact<'a>(amp;'a mut self, buf: amp;'a mut [u8]) -> ReadExact<'a, Self>
     |            ---------- the trait cannot be made into an object because method `read_exact` references the `Self` type in its return type
  

Я не уверен, как поступить. Я рассматривал гигантский impl блок для enum оболочки, но это, похоже, больше работы, чем match блоки. На языке OO был бы родительский класс или интерфейс, поэтому я исследовал trait_enum crate для автоматизации создания оболочки impl , но у меня было много проблем с тем, чтобы заставить это работать.

На данный момент единственная очистка, которая, я уверен, сработает, — это переместить обходной путь в макрос или функцию.

Я был бы признателен за любые отзывы о лучшем способе сделать это. 🙂

РЕДАКТИРОВАТЬ: по предложению пользователя 4815162342 я сделал структуру универсальной по типу AsyncReadExt , и это, похоже, работает для моего примера. Попробую позже в моем более крупном проекте.

 use tokio::io::AsyncReadExt;

struct Test<T: AsyncReadExt> {
  test: T,
}

async fn myfn<T: AsyncReadExt>(mut t: Test<T>) where T: std::marker::Unpin {
    let mut v = Vec::<u8>::new();
    t.test.read_buf(amp;mut v).await;
}

fn main () {}
  

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

1. Рассматривали ли вы возможность сделать свою структуру универсальной для признака? Например, struct Test<T: AsyncReadExt> { test: T, } ? И если вам нужно поместить разные Test<T> объекты в одно и то же поле, вы можете создать свой собственный признак, который безопасен для объектов, и использовать Box<dyn ThatTrait> для хранения ваших структур.

2. Интересно, спасибо! Мне пришлось добавить предложение «where T: Unpin» в общий, но я получил свой пример для компиляции

Ответ №1:

Чтобы превратить AsyncRead объект в признак, вы должны использовать тип Pin<Box<dyn AsyncRead>> .

 use std::pin::Pin;
use tokio::io::AsyncRead;

struct Test {
    test: Pin<Box<dyn AsyncRead>>,
}

impl Test {
    fn new<T: AsyncRead>(io: T) -> Self {
        Self {
            test: Box::pin(io),
        }
    }
}
  

AsyncReadExt Признак — это признак расширения, и вы всегда должны использовать AsyncRead признак при упоминании типа AsyncRead .