使用(可变的)借用参数调用通用异步函数

Kil*_*nDS 12 rust async-await

我的问题的最小例子。

use std::future::Future;

async fn call_changer<'a, F, Fut>(changer: F)
where
    F: FnOnce(&'a mut i32) -> Fut,
    Fut: Future<Output = ()> + 'a,
{
    let mut i = 0;
    changer(&mut i).await; // error 1
    dbg!(i); // error 2
}

#[tokio::main]
async fn main() {
    call_changer(|i| async move {
        *i = 100;
    })
    .await;
}
Run Code Online (Sandbox Code Playgroud)

这会导致两个相关的错误,请参阅Rust Playground以获取详细输出:

  1. 借物的寿命不够长,因为掉落在身体i的末端。call_changer
  2. i不能在await之后使用,因为它仍然被可变地借用。

我对两者都感到有点惊讶,我理解为什么Future返回需要与其借用F具有相同的生命周期( )(相关的异步书籍部分)。然而,根据同一个参考文献,一旦我调用了 wait on的结果,借用就应该结束,这显然不会发生,否则我就不会出现这些错误。将此示例重写为类似本书的内容,其中函数不作为参数传递,而是直接调用,按预期工作。'achangerchanger

这是怎么回事?我能做些什么吗?&mut用构造替换Rc<RefCell<_>>可以按预期工作,但如果可能的话我想避免这种情况。

Cha*_*man 22

当您指定'a为通用参数时,您的意思是“我允许调用者选择它想要的任何生命周期”。'static例如,调用者也可以选择。那么你就答应通过&'a mut i32,也就是&'static mut i32。但i不为之而活'static!这就是第一个错误的原因。

第二个错误是因为你承诺你会i可变地借钱'a。但同样,'a即使您丢弃了结果,也可能会覆盖整个函数!'static例如,调用者可以选择,然后将引用存储在全局变量中。如果你使用iafter,则您可以在可变借用时使用它。繁荣!

你想要的不是让调用者选择生命周期,而是说“我正在向你传递一个具有某个生命周期的参考'a,我希望你还给我一个具有相同生命周期的未来”。我们用来达到“我给你一些寿命,但让我选择哪一个”的效果的东西叫做HRTB(Higher-Kinded Trait Bounds)。

如果您只想返回特定类型,而不是泛型类型,则它看起来像:

async fn call_changer<'a, F, Fut>(changer: F)
where
    F: for<'a> FnOnce(&'a mut i32) -> &'a mut i32,
{ ... }
Run Code Online (Sandbox Code Playgroud)

Box<dyn Future>您也可以使用以下语法:

use std::future::Future;
use std::pin::Pin;

async fn call_changer<F>(changer: F)
where
    F: for<'a> FnOnce(&'a mut i32) -> Pin<Box<dyn Future<Output = ()> + 'a>>,
{
    let mut i = 0;
    changer(&mut i).await;
    dbg!(i);
}

#[tokio::main]
async fn main() {
    call_changer(|i| {
        Box::pin(async move {
            *i = 100;
        })
    })
    .await;
}
Run Code Online (Sandbox Code Playgroud)

游乐场。

事实上,您甚至可以摆脱显式for子句,因为 HRTB 是闭包中生命周期的默认脱糖:

where
    F: FnOnce(&mut i32) -> &mut i32,
Run Code Online (Sandbox Code Playgroud)
where
    F: FnOnce(&mut i32) -> Pin<Box<dyn Future<Output = ()> + '_>>,
Run Code Online (Sandbox Code Playgroud)

剩下的唯一问题是:我们如何用 generic 来表达这一点Fut?

尝试将其应用于for<'a>多个条件是很诱人的:

where
    for<'a>
        F: FnOnce(&'a mut i32) -> Fut,
        Fut: Future<Output = ()> + 'a,
Run Code Online (Sandbox Code Playgroud)

或者:

where
    for<'a> FnOnce(&'a mut i32) -> (Fut + 'a),
    Fut: Future<Output = ()>,
Run Code Online (Sandbox Code Playgroud)

但不幸的是,两者都不起作用。

我们可以做什么?

一种选择是留下来Pin<Box<dyn Future>>。

另一种是使用自定义特征:

trait AsyncSingleArgFnOnce<Arg>: FnOnce(Arg) -> <Self as AsyncSingleArgFnOnce<Arg>>::Fut {
    type Fut: Future<Output = <Self as AsyncSingleArgFnOnce<Arg>>::Output>;
    type Output;
}

impl<Arg, F, Fut> AsyncSingleArgFnOnce<Arg> for F
where
    F: FnOnce(Arg) -> Fut,
    Fut: Future,
{
    type Fut = Fut;
    type Output = Fut::Output;
}

async fn call_changer<F>(changer: F)
where
    F: for<'a> AsyncSingleArgFnOnce<&'a mut i32, Output = ()>,
{
    let mut i = 0;
    changer(&mut i).await;
    dbg!(i);
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,这不适用于闭包。我不知道为什么。你必须放一个fn:

#[tokio::main]
async fn main() {
    async fn callback(i: &mut i32) {
        *i += 100;
    }
    call_changer(callback).await;
}
Run Code Online (Sandbox Code Playgroud)

游乐场。

了解更多信息: