tokio::net::TcpStream::connect
— это асинхронная функция, то есть она возвращает экзистенциальный тип, impl Future
. Я хотел бы сохранить Vec
этих фьючерсов в структуре. Я нашел много вопросов, когда кто-то хочет сохранить несколько различных impl Future
в списке, но я хочу сохранить только возвращаемый тип одного. Я чувствую, что это должно быть возможно без Box<dyn Future>
, так как я действительно храню только один конкретный тип, но я не могу понять, как это сделать, не получая ошибок found opaque type
.
Могу ли я сохранить `impl Future` как конкретный тип?
Ответы (2)
Это возможно с ночной функцией min_type_alias_impl_trait
. Хитрость заключается в создании псевдонима типа и фиктивной функции, из которой компилятор может вывести определяющее использование.
#![feature(min_type_alias_impl_trait)]
use tokio::net::TcpStream;
use core::future::Future;
type TcpStreamConnectFut = impl Future<Output = std::io::Result<TcpStream>>;
fn __tcp_stream_connect_defining_use() -> TcpStreamConnectFut {
TcpStream::connect("127.0.0.1:8080")
}
struct Foo {
connection_futs: Vec<TcpStreamConnectFut>
}
Это компилируется, но не работает должным образом:
impl Foo {
fn push(&mut self) {
self.connection_futs.push(TcpStream::connect("127.0.0.1:8080"));
}
}
error[E0308]: mismatched types
--> src/lib.rs:18:35
|
6 | type TcpStreamConnectFut = impl Future<Output = std::io::Result<TcpStream>>;
| ------------------------------------------------ the expected opaque type
...
18 | self.connection_futs.push(TcpStream::connect("127.0.0.1:8080"));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected opaque type, found a different opaque type
|
= note: while checking the return type of the `async fn`
= note: expected opaque type `impl Future` (opaque type at <src/lib.rs:6:28>)
found opaque type `impl Future` (opaque type at </playground/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.7.1/src/net/tcp/stream.rs:111:56>)
= help: consider `await`ing on both `Future`s
= note: distinct uses of `impl Trait` result in different opaque types
Использование созданной нами фиктивной функции работает:
impl Foo {
fn push(&mut self) {
self.connection_futs.push(__tcp_stream_connect_defining_use());
}
}
Итак, мы можем просто создать функции-оболочки:
fn tcp_stream_connect<A: ToSocketAddrs>(addr: A) -> TcpStreamConnectFut {
TcpStream::connect(addr)
}
Кроме...
error: type parameter `A` is part of concrete type but not used in parameter list for the `impl Trait` type alias
--> src/main.rs:9:74
|
9 | fn tcp_stream_connect<A: ToSocketAddrs>(addr: A) -> TcpStreamConnectFut {
| __________________________________________________________________________^
10 | | TcpStream::connect(addr)
11 | | }
| |_^
Мы могли бы просто использовать String
или &'static str
, и все это скомпилировалось:
type TcpStreamConnectFut = impl Future<Output = std::io::Result<TcpStream>>;
fn tcp_stream_connect(addr: &'static str) -> TcpStreamConnectFut {
TcpStream::connect(addr)
}
struct Foo {
connection_futs: Vec<TcpStreamConnectFut>
}
impl Foo {
fn push(&mut self) {
self.connection_futs.push(tcp_stream_connect("..."));
}
}
Вы также можете добавить универсальный параметр к самому псевдониму типа, но в данном случае это, вероятно, не имеет смысла:
type TcpStreamConnectFut<A> = impl Future<Output = std::io::Result<TcpStream>>;
fn tcp_stream_connect<A: ToSocketAddrs>(addr: A) -> TcpStreamConnectFut<A> {
TcpStream::connect(addr)
}
struct Foo {
connection_futs: Vec<TcpStreamConnectFut<&'static str>>
}
impl Foo {
fn push(&mut self) {
self.connection_futs.push(tcp_stream_connect("..."));
}
}
Так что это возможно, но есть пара ограничений. Я не уверен, сколько из них являются ошибками, а сколько преднамеренным поведением. Было обсуждение оператора typeof
, чтобы сделать это проще, но это то, что у нас есть на данный момент.
Вы можете просто использовать старый добрый Vec
, вот так:
use core::future::Future;
use tokio::net::TcpStream;
fn just_vec() -> Vec<impl Future<Output = std::io::Result<TcpStream>>> {
let mut v = Vec::new();
// connect to several streams
v.push(TcpStream::connect("127.0.0.1:8080"));
v.push(TcpStream::connect("127.0.0.2:8080"));
v
}
Однако если вы хотите сохранить его в структуре, становится сложнее, потому что, в отличие от предыдущего случая, где можно вывести конкретный тип, вам нужно быть более явным со структурами.
Одним из стабильных способов сделать это является использование универсальной структуры. На самом деле это очень похоже на сохранение замыкания (где у вас также нет конкретного типа).
use core::future::Future;
use tokio::net::TcpStream;
use tokio::io::AsyncWriteExt;
struct Foo<T> {
connections: Vec<T>
}
/// This is just like any other vec-wrapper
impl<T> Foo<T> {
pub fn new() -> Self {
Self {
connections: Vec::new(),
}
}
pub fn push(&mut self, conn: T) {
self.connections.push(conn);
}
}
/// Some more specific functios that actually need the Future
impl<T> Foo<T> where T: Future<Output = std::io::Result<TcpStream>> {
pub async fn broadcast(self, data: &[u8]) -> std::io::Result<()> {
for stream in self.connections {
stream.await?.write_all(data).await?
}
Ok(())
}
}
async fn with_struct() -> std::io::Result<()> {
let mut foo = Foo::new();
// connect to several streams
foo.push(TcpStream::connect("127.0.0.1:8080"));
foo.push(TcpStream::connect("127.0.0.2:8080"));
// Do something with the connections
foo.broadcast(&[1,2,3]).await
}
Да, это тоже работает, выдвигая работу туда, где тип может быть выведен компилятором. Однако это работает не для всех случаев.