在下面的例子中,有两个闭包,一个通过了编译器,另一个失败了,为什么?
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
”。
既然closure1
和closure2
都使用外部的s
,为什么只有b
和impl Copy trait
会导致错误?
通常运行完closure1
后,s
会消失,但它返回的async块甚至没有开始运行,所以当运行它时,如何捕获一个不存在的s
?
rust编译器是否有一些特殊情况,或者我错过了一些基本知识?
1条答案
按热度按时间irtuqstp1#
因为
closure1
和closure2
都借用s
但他们不:由于
String
不是Copy
,因此当您写入:这必然是移动,因此需要将
s
移动到async
块中。同时由于
bool
是Copy
是副本,因此仅需要通过引用捕获
b
。这是因为
async
块更接近闭包,而不是“普通”块,并且将类似地执行它们所需的“最松散”捕获(如果内部的所有操作都对引用起作用,则它们将通过引用捕获)。如果你允许s
通过引用被捕获,你可以看到这一点:现在,由于与
b
相同的原因,它将不再编译。这就是为什么
async move
块存在的原因,它具有类似的移动闭包的语义:他们用价值来捕捉一切,如果你需要任何借款,你可以自己安排他们。如果您将区块转换为async move
,问题将得到解决,因为b
将首先被移动(复制)到异步区块中,然后它将再次复制到b1
中。它还消除了对语言环境的需要,因为println!
的引用功能将被忽略:编译良好。
IME对于
async
块来说,这实际上比闭包更糟糕,因为异步块“总是转义”其封闭函数,而闭包通常不会。虽然主要的问题是异步闭包还不稳定,但它们的稳定性应该使异步块的必要性降低很多,从而减轻它们的脚步声。我不知道这样做是否有缺点,但我建议避免使用
async
块并始终使用async move
,这使得行为更加规则,并且与闭包不同,它不会限制它们,因为借用异步块很少工作。