【问题标题】:Calling a generic async function with a (mutably) borrowed argument使用(可变的)借用参数调用通用异步函数
【发布时间】:2023-01-08 18:58:50
【问题描述】:

我的问题的最小例子。

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;
}

这会导致两个相关错误,请参阅rust playground 了解详细输出:

  1. 借用的时间不够长,因为icall_changer的主体末尾被丢弃。
  2. i 不能在 await 之后使用,因为它仍在被可变地借用。

    我对两者都有点惊讶,我理解为什么 FFuture 返回需要与其借用 (relevant async book section) 具有相同的生命周期 ('a)。但是,根据同一参考资料,一旦我调用 changer 的结果等待,借用就应该结束,这显然不会发生,否则我不会有这些错误。将此示例改写为类似书中的内容,其中 changer 函数未作为参数传入,而是直接调用,按预期工作。

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

【问题讨论】:

    标签: rust async-await


    【解决方案1】:

    当您将 'a 指定为通用参数时,您的意思是“我允许调用者选择它想要的任何生命周期”。例如,来电者也可以选择'static。然后你承诺传&amp;'a mut i32,即&amp;'static mut i32。但是i并不为'static而活!这就是第一个错误的原因。

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

    你想要的是不是让来电者选择生命周期,而不是说“我正在向你传递一个参考一些lifetime 'a,我要你还我一个有相同生命的未来”。我们用什么来实现“我给你一些生命,但让我选择”的效果是HRTB(更高种类的特征界限)。

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

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

    您也可以通过以下语法使用 Box&lt;dyn Future&gt;

    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;
    }
    

    Playground

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

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

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

    尝试将 for&lt;'a&gt; 应用于多个条件很诱人:

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

    或者:

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

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

    我们可以做什么?

    一种选择是留在Pin&lt;Box&lt;dyn Future&gt;&gt;

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

    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);
    }
    

    不幸的是,这不适用于闭包。我不知道为什么。你必须输入fn

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

    Playground

    想要查询更多的信息:

    【讨论】:

    • 谢谢,第二次你提到 'static 我知道我的推理哪里出了问题,又一次忘记了 for&lt;'a&gt;,但你的回答非常详细和有帮助!
    • 作为后续行动:我试图通过减少实现 FnOnce(&amp;'a mut Arg) 的通用性来使闭包工作,Arg'static,我点击了 this rust issue。所以这似乎是目前的语言限制。
    猜你喜欢
    • 1970-01-01
    • 2019-12-12
    • 1970-01-01
    • 1970-01-01
    • 2012-07-25
    • 1970-01-01
    • 1970-01-01
    • 2022-12-04
    • 2017-05-02
    相关资源
    最近更新 更多