遇到这种有趣的情况时,我正在玩Rust闭包:
fn main() {
let mut y = 10;
let f = || &mut y;
f();
}
这给出了一个错误:
error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
--> src/main.rs:4:16
|
4 | let f = || &mut y;
| ^^^^^^
|
note: first, the lifetime cannot outlive the lifetime as defined on the body at 4:13...
--> src/main.rs:4:13
|
4 | let f = || &mut y;
| ^^^^^^^^^
note: ...so that closure can access `y`
--> src/main.rs:4:16
|
4 | let f = || &mut y;
| ^^^^^^
note: but, the lifetime must be valid for the call at 6:5...
--> src/main.rs:6:5
|
6 | f();
| ^^^
note: ...so type `&mut i32` of expression is valid during the expression
--> src/main.rs:6:5
|
6 | f();
| ^^^
即使编译器试图逐行解释它,但我仍然不明白它到底在抱怨什么。
是否要说可变引用不能超过封闭的闭包?
如果删除调用f()
,编译器不会抱怨。
答案 0 :(得分:7)
闭包f
存储对y
的可变引用。如果允许它返回此引用的副本,则最终将同时对y
进行两个可变的引用(一个在闭包中,一个返回),Rust的内存安全规则禁止该引用。
闭包可以认为是
struct __Closure<'a> {
y: &'a mut i32,
}
由于它包含一个可变的引用,因此闭包称为FnMut
,本质上带有定义
fn call_mut(&mut self, args: ()) -> &'a mut i32 { self.y }
由于我们仅对闭包本身具有可变引用,因此我们无法将字段y
从借用上下文中移出,我们也无法复制该字段,因为可变引用不是{{1 }}。
我们可以通过强制将闭包称为Copy
而不是FnOnce
来欺骗编译器接受代码。这段代码可以正常工作:
FnMut
由于我们在闭包范围内使用了fn main() {
let x = String::new();
let mut y: u32 = 10;
let f = || {
drop(x);
&mut y
};
f();
}
,而x
不是x
,因此编译器检测到闭包只能是Copy
。调用FnOnce
闭包将按值本身传递闭包,因此我们可以将可变引用移出。
强制闭包为FnOnce
的另一种更明确的方法是将其传递给具有特征绑定的泛型函数。该代码也可以正常工作:
FnOnce
答案 1 :(得分:6)
考虑以下代码:
fn main() {
let mut y: u32 = 10;
let ry = &mut y;
let f = || ry;
f();
}
之所以起作用,是因为编译器能够推断ry
的生存期:引用ry
处于y
的同一范围内。
现在等价的代码:
fn main() {
let mut y: u32 = 10;
let f = || {
let ry = &mut y;
ry
};
f();
}
现在,编译器将与闭包主体范围相关联的生存期分配给ry
,而不是与主体相关联的生存期。
还请注意,不变的参考案例有效:
fn main() {
let mut y: u32 = 10;
let f = || {
let ry = &y;
ry
};
f();
}
这是因为&T
具有复制语义,而&mut T
具有移动语义,有关更多详细信息,请参见Copy/move semantics documentation of &T/&mut T types itself。
编译器抛出与生存期相关的问题:
cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
但是,正如Sven Marnach所指出的,还有一个问题与禁止从借来的内容中移出有关。
但是为什么编译器不会抛出此错误?即:
cannot move out of borrowed content
简短的答案是,编译器首先执行类型检查,然后再借用检查。
闭包由两部分组成:
闭包的状态:包含闭包捕获的所有对象的结构
闭包的逻辑:FnOnce
,FnMut
或Fn
特征的实现
在这种情况下,闭包的状态是可变引用y
,逻辑是闭包{&mut y}
的主体,它仅返回可变引用。
遇到参考时,锈控制两个方面:
状态:如果引用指向有效的内存片,(即读取生存期有效性);
逻辑:如果内存片是别名的,换句话说,如果它同时从多个引用中指向;
请注意,为避免内存混淆,禁止从借用内容中移出。
rustc
编译器通过stages(这里是简化的工作流程)执行工作:
.rs input -> AST -> HIR -> hir postprocessing -> MIR -> mir postprocessing -> llvm IR -> binary
编译器报告了生命周期问题,因为rustc
首先在hir postprocessing
中执行类型检查阶段,该阶段包括生命周期analisys,然后,如果成功,则在mir postprocessing
阶段执行借位检查
答案 2 :(得分:5)
(目前,这只是有根据的猜测。如果人们似乎同意这个答案是正确的,我将删除此免责声明)
这里有两个主要作用:
闭包不能返回寿命为self
(闭包对象)的任何引用。这是为什么?每个闭包都可以称为FnOnce
,因为这是FnMut
的超特征,而反过来又是Fn
的超特征。 FnOnce
使用此方法:
fn call_once(self, args: Args) -> Self::Output;
请注意,self
是按值传递的。因此,由于self
已被消耗(并且现在位于call_once
函数中),我们无法返回对其的引用-等同于返回对局部函数变量的引用。
理论上,call_mut
将允许返回对self
的引用(因为它收到了&mut self
)。但是由于call_once
,call_mut
和call
都是用同一主体实现的,所以闭包通常不能返回对self
的引用(即:对其捕获的环境的引用)。 / p>
只需确保:闭包可以捕获引用并将其返回!他们可以通过引用捕获并返回该引用。那些事情有些不同。它与闭包类型中存储的内容有关。如果类型中存储了引用,则可以将其返回。但是我们不能返回对封闭类型中存储的任何内容的引用。
考虑此函数(请注意,参数类型暗示'inner: 'outer
; 'outer
比'inner
短):
fn foo<'outer, 'inner>(x: &'outer mut &'inner mut i32) -> &'inner mut i32 {
*x
}
这不会编译。乍一看,它似乎应该编译,因为我们只是剥离了一层引用。它确实适用于不可变的引用!但是可变的引用在这里可以保持健全性。
不过可以返回&'outer mut i32
。但是要获得更长(内部)寿命的直接参考是不可能的。
让我们尝试编写您要编写的闭包代码:
let mut y = 10;
struct Foo<'a>(&'a mut i32);
impl<'a> Foo<'a> {
fn call<'s>(&'s mut self) -> &'??? mut i32 { self.0 }
}
let mut f = Foo(&mut y);
f.call();
返回的参考应具有哪个生存期?
'a
,因为我们基本上有一个&'s mut &'a mut i32
。如上所述,在这种嵌套的可变参考情况下,我们无法提取更长的寿命!'s
,因为那将意味着闭包返回生命周期为'self
的内容(“从self
借来的”)。而且如上所述,闭包无法做到这一点。因此,编译器无法为我们生成闭包impls。