Reference 为什么我不能从闭包返回外部变量的可变引用?
当我遇到这个有趣的场景时,我正在玩生锈的闭包:Reference 为什么我不能从闭包返回外部变量的可变引用?,reference,rust,closures,lifetime,mutable,Reference,Rust,Closures,Lifetime,Mutable,当我遇到这个有趣的场景时,我正在玩生锈的闭包: fn main() { let mut y = 10; let f = || &mut y; f(); } 这会产生一个错误: 错误[E0495]:由于需求冲突,无法推断借用表达式的适当生存期 -->src/main.rs:4:16 | 4 |设f=| |&mut y; | ^^^^^^ | 注意:首先,寿命不能超过身体上定义的4:13的寿命。。。 -->src/main.rs:4
fn main() {
let mut y = 10;
let f = || &mut y;
f();
}
这会产生一个错误:
错误[E0495]:由于需求冲突,无法推断借用表达式的适当生存期
-->src/main.rs:4:16
|
4 |设f=| |&mut y;
| ^^^^^^
|
注意:首先,寿命不能超过身体上定义的4:13的寿命。。。
-->src/main.rs:4:13
|
4 |设f=| |&mut y;
| ^^^^^^^^^
注意:…以便闭包可以访问'y'`
-->src/main.rs:4:16
|
4 |设f=| |&mut y;
| ^^^^^^
注意:但是,生命周期必须对6:5的呼叫有效。。。
-->src/main.rs:6:5
|
6 | f();
| ^^^
注意:…因此表达式的类型“%mut i32”在表达式运行期间有效
-->src/main.rs:6:5
|
6 | f();
| ^^^
即使编译器试图逐行解释它,我仍然不明白它到底在抱怨什么
它是在试图说可变引用不能在封闭闭包之外生存吗
如果我删除调用f()
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
具有移动语义,有关详细信息,请参阅
丢失的那一块
编译器抛出与生存期相关的错误:
cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
但正如斯文·马纳奇所指出的,也有一个与错误相关的问题
cannot move out of borrowed content
但是为什么编译器不抛出这个错误呢
简而言之,编译器首先执行类型检查,然后执行借用检查
长话短说
封口由两部分组成:
- 闭包的状态:包含闭包捕获的所有变量的结构
- 闭包的逻辑:实现
FnOnce
、FnMut
或Fn
特征
在这种情况下,闭包的状态是可变引用y
,逻辑是只返回可变引用的闭包体{&mut y}
遇到参考时,锈蚀控制两个方面:
状态:如果引用指向有效内存片(即生存期有效性的只读部分)
逻辑:如果内存片有别名,换句话说,如果它同时指向多个引用
注意:为了避免内存混叠,禁止从借用内容移出
Rust编译器通过以下简化的工作流执行其工作:
.rs input -> AST -> HIR -> HIR postprocessing -> MIR -> HIR postprocessing -> LLVM IR -> binary
编译器报告生存期问题,因为它首先在HIR postprocessing
(包括生存期分析)中执行类型检查阶段,然后,如果成功,在MIR postprocessing
阶段执行借用检查。这里有两个主要问题:
闭包不能返回对其环境的引用
对可变引用的可变引用只能使用外部引用的生存期(与不可变引用不同)
返回对环境的引用的闭包
闭包不能返回生存期为self
(闭包对象)的任何引用。为什么呢?每个闭包都可以称为FnOnce
,因为这是FnMut
的超级特性,而这又是Fn
的超级特性FnOnce
有以下方法:
fn call_once(self, args: Args) -> Self::Output;
请注意,self
是通过值传递的。因此,由于self
已被使用(现在位于call\u once
函数中),我们无法返回对它的引用——这相当于返回对局部函数变量的引用
理论上,调用mut
将允许返回对self
的引用(因为它接收和mut self
)。但是由于call\u once
、call\u mut
和call
都是用同一个主体实现的,所以闭包通常不能返回对self
的引用(即:对其捕获的环境的引用)
可以肯定的是:闭包可以捕获引用并返回这些引用!他们可以通过引用捕获并返回引用。这些东西是不同的。它只是关于闭包类型中存储的内容。如果类型中存储有引用,则可以返回该引用。但是我们不能返回对存储在闭包类型中的任何内容的引用
嵌套可变引用
考虑此函数(请注意,参数类型意味着'inner:'outer
;'outer
比'inner
短):
返回的引用应该具有什么生存期
- 它不可能是
'a
,因为我们基本上有一个&的mut&'a mut i32
。如上所述,在这种嵌套可变引用情况下,我们无法提取更长的生命周期李>
- 但它也不能是
的
,因为这意味着闭包返回的东西的生命周期为'self
(“借用自self
”)。如上所述,闭包不能做到这一点
所以编译器不能为我们生成闭包impls 短版
闭包f
存储对y
的可变引用。如果允许它返回此引用的副本,您将得到两个simult
fn foo<'outer, 'inner>(x: &'outer mut &'inner mut i32) -> &'inner mut i32 {
*x
}
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();
struct __Closure<'a> {
y: &'a mut i32,
}
fn call_mut(&mut self, args: ()) -> &'a mut i32 { self.y }
fn main() {
let x = String::new();
let mut y: u32 = 10;
let f = || {
drop(x);
&mut y
};
f();
}
fn make_fn_once<'a, T, F: FnOnce() -> T>(f: F) -> F {
f
}
fn main() {
let mut y: u32 = 10;
let f = make_fn_once(|| {
&mut y
});
f();
}