如何在rust的`poll`中调用带有非静态引用的异步函数?

gojuced7  于 2023-08-05  发布在  其他
关注(0)|答案(2)|浏览(104)

我在尝试实现Future时遇到了一个障碍。
我需要在我的poll函数中poll另一个async函数,它只接受&str。它看起来像这样:

async fn make_query(query: &str) -> i32 {
    // Magic happens
    5
}

字符串
由于这是一个来自另一个库的函数,我不能简单地改变它的签名来获得值的所有权。
现在我的结构Executor,我想实现Future,它包含一个String,当调用make_query时应该引用它:

struct Executor {
    query: String,
    future: Mutex<Option<Pin<Box<dyn Future<Output = i32>>>>>
}

impl Future for Executor {
    type Output = i32;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let mut lock = self.future.lock().unwrap();

        if let None = *lock {
            // This is where the error occurs
            *lock = Some(Box::pin(make_query(&self.query)))
        }

        let future = lock.as_mut().unwrap();
        pin!(future).poll(cx)
    }
}


现在,这样做的目的是我可以.await my Executor自动进行查询:

#[tokio::main]
async fn main() {
    let result = Executor {
        query: "foo".to_string(),
        future: Mutex::new(None)
    }.await;

    println!("Result is {result}");
}


现在,显然Mutex要求传递给它的每个引用都是'static?我收到了这些错误消息:

error: lifetime may not live long enough
  --> src/main.rs:23:26
   |
16 |     fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
   |                       - let's call the lifetime of this reference `'1`
...
23 |             *lock = Some(Box::pin(make_query(&self.query)))
   |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cast requires that `'1` must outlive `'static`

error[E0597]: `self` does not live long enough
  --> src/main.rs:23:47
   |
16 |     fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
   |             ---- binding `self` declared here
...
23 |             *lock = Some(Box::pin(make_query(&self.query)))
   |                          ---------------------^^^^--------
   |                          |                    |
   |                          |                    borrowed value does not live long enough
   |                          cast requires that `self` is borrowed for `'static`
...
29 |     }
   |     - `self` dropped here while still borrowed


如何告诉Rust Executor只有在被完全轮询时才会被丢弃?我不能改变poll的函数签名,因为这会违反trait规范。
或者,更有可能的是,做我想做的事情的正确方法是什么?
这里有一个playground的链接。
PS:我知道调用.await来执行查询可能会被认为是一种“反模式”。

溶液

查看drewtato's answer以获得解释。
解决方案(对我来说)是从我的结构体中删除future字段:

struct Executor {
    query: String
}


然后,不实现Future,而是实现IntoFuture,当使用.await时,它会被隐式调用。在这里,我们可以使用async move {}来移动值,而不仅仅是引用它。
注意,此时type IntoFuture = impl Future是不稳定的,因此我不得不使用Pin<Box<dyn Future>>

impl IntoFuture for Executor {
    type Output = i32;
    type IntoFuture = Pin<Box<dyn Future<Output = Self::Output>>>;

    fn into_future(self) -> Self::IntoFuture {
        Box::pin(async move {
            make_query(&self.query).await
        })
    }
}


现在你可以在Executor上调用.await

#[tokio::main] 
async fn main() {
    let res = Executor {
        query: "foo".into()
    }.await;

    println!("Result is {res}"); // 5
}

kuuvgm7e

kuuvgm7e1#

你想做一个self-referential struct, which is not safely possible
看起来你试图让这个异步函数'static,如果你试图派生它,这是有用的。你可以通过创建一个async block来实现这一点。

#[tokio::main]
async fn main() {
    let s = "foo".to_string();
    
    // This doesn't work since it borrows `s`.
    // let result = tokio::spawn(make_query(&s)).await.unwrap();

    // This works since it moves `s` into the future.
    let result = tokio::spawn(async move {
        make_query(&s).await
    }).await.unwrap();

    println!("Result is {result}");
}

字符串
这个异步块实际上是自引用的,但是异步块是由rust专门处理的。如果你想手工创建它,你将需要不安全的代码。
注意,calling 一个async函数不起作用,只有 * waiting * 起作用,所以延迟make_query调用没有任何意义。如果你想在make_query之前做一些事情,你可以简单地把它放在async块中。
我不明白你为什么要用互斥锁。如果你需要这个,把它加到问题里。完全切除可能没问题。
每个trait对象都包含一个生存期,所以当你声明一个trait对象而没有像dyn Future<Output = i32>这样的生存期时,它通常会变成dyn Future<Output = i32> + 'static。如果你想在trait对象中存储非'static数据,你需要将它添加到你的类型中。

struct Executor<'f> {
    query: String,
    future: Mutex<Option<Pin<Box<dyn Future<Output = i32> + 'f>>>>
}


然而,只有当你的结构不是自引用的时候,这才是正确的。

iugsix8n

iugsix8n2#

处理的方法是创建一个 Package 器函数,该函数接受ownedString,完成工作,并返回String,以便重用它。这样,你得到了一个不包含生存期的future(Rust编译器为你处理自引用),你可以自由地将它存储在结构体中:
我还将向您介绍tokio_util::sync::ReusableBoxFuture,它允许您在这种情况下通过只为将来分配一次空间来保存分配。在这种情况下,您不需要它,因为您只使用了一次未来,但在类似的情况下,它可能很有用。

相关问题