rust 关于异步块的错误超过当前函数

wvt8vs2t  于 2023-05-17  发布在  其他
关注(0)|答案(1)|浏览(90)

在下面的例子中,有两个闭包,一个通过了编译器,另一个失败了,为什么?

fn f() {
    let closure1 = |s: String| async {
        let s1 = s;
        println!("{s1}");
    };
    
    let closure2 = |s: String, b: bool| async {
        let s1 = s;
        let b1 = b;
        println!("{s1} {b1}");
    };
}

closure1是正确的,但closure2是错误的,错误是“async block may outlive the current function,but it borrows b,which is owned by the current function may outlive borrowed value b”。
既然closure1closure2都使用外部的s,为什么只有bimpl Copy trait会导致错误?
通常运行完closure1后,s会消失,但它返回的async块甚至没有开始运行,所以当运行它时,如何捕获一个不存在的s
rust编译器是否有一些特殊情况,或者我错过了一些基本知识?

irtuqstp

irtuqstp1#

因为closure1closure2都借用s
但他们不:由于String不是Copy,因此当您写入:

let s1 = s;

这必然是移动,因此需要将s移动到async块中。
同时由于boolCopy

let b1 = b;

是副本,因此仅需要通过引用捕获b
这是因为async块更接近闭包,而不是“普通”块,并且将类似地执行它们所需的“最松散”捕获(如果内部的所有操作都对引用起作用,则它们将通过引用捕获)。如果你允许s通过引用被捕获,你可以看到这一点:

let s1 = s.clone();

现在,由于与b相同的原因,它将不再编译。
这就是为什么async move块存在的原因,它具有类似的移动闭包的语义:他们用价值来捕捉一切,如果你需要任何借款,你可以自己安排他们。如果您将区块转换为async move,问题将得到解决,因为b将首先被移动(复制)到异步区块中,然后它将再次复制到b1中。它还消除了对语言环境的需要,因为println!的引用功能将被忽略:

fn f() {
    let closure1 = |s: String| async move {
        println!("{s}");
    };
    
    let closure2 = |s: String, b: bool| async move {
        println!("{s} {b}");
    };
}

编译良好。
IME对于async块来说,这实际上比闭包更糟糕,因为异步块“总是转义”其封闭函数,而闭包通常不会。虽然主要的问题是异步闭包还不稳定,但它们的稳定性应该使异步块的必要性降低很多,从而减轻它们的脚步声。
我不知道这样做是否有缺点,但我建议避免使用async块并始终使用async move,这使得行为更加规则,并且与闭包不同,它不会限制它们,因为借用异步块很少工作。

相关问题