我可以将`impl Future`作为具体类型进行存储吗?

4

tokio::net::TcpStream::connect是一个异步函数,意味着它返回一个存在类型impl Future。我想在一个结构体中存储这些futures的Vec。我找到了许多问题,其中有人想要在列表中存储多个不同的impl Future,但我只想存储一个的返回类型。我觉得这应该是可能的,而不需要使用Box<dyn Future>,因为我实际上只存储了一个具体类型,但我无法找出如何做到这一点,而不会出现found opaque type错误。

2个回答

4

使用每晚构建功能中的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)
}

Except...

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操作符来简化此操作,但目前我们只能用这个方法。


1
您可以像这样使用一个好的老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
}

是的,这也可以工作,通过将工作推到一个地方,类型可以被编译器推断出来。但并不适用于所有情况。 - Ibraheem Ahmed

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接