reference - 可变引用生命周期与不可变引用生命周期
问题描述
我有以下代码:
struct Bar<T> {
k: [T; 10],
}
impl<T> Bar<T> {
fn thing(&self, i: usize) -> &T {
&self.k[i]
}
fn thing_mut(&mut self, i: usize) -> &mut T {
&mut self.k[i]
}
}
struct Foo<'a, T: 'a> {
bar: &'a Bar<T>,
count: usize,
}
impl<'a, T> Foo<'a, T> {
fn get(&mut self) -> Option<&'a T> {
if self.count < 10 {
let thing = self.bar.thing(self.count);
self.count += 1;
Some(thing)
} else {
None
}
}
}
struct FooMut<'a, T: 'a> {
bar: &'a mut Bar<T>,
count: usize,
}
impl<'a, T> FooMut<'a, T> {
fn get(&mut self) -> Option<&'a mut T> {
if self.count < 10 {
let thing = self.bar.thing_mut(self.count);
self.count += 1;
Some(thing)
} else {
None
}
}
}
Foo
编译,但FooMut
不:
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
--> src/main.rs:40:34
|
40 | let thing = self.bar.thing_mut(self.count);
| ^^^^^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 38:5...
--> src/main.rs:38:5
|
38 | / fn get(&mut self) -> Option<&'a mut T> {
39 | | if self.count < 10 {
40 | | let thing = self.bar.thing_mut(self.count);
41 | | self.count += 1;
... |
45 | | }
46 | | }
| |_____^
note: ...so that reference does not outlive borrowed content
--> src/main.rs:40:25
|
40 | let thing = self.bar.thing_mut(self.count);
| ^^^^^^^^
note: but, the lifetime must be valid for the lifetime 'a as defined on the impl at 37:1...
--> src/main.rs:37:1
|
37 | impl<'a, T> FooMut<'a, T> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^
= note: ...so that the expression is assignable:
expected std::option::Option<&'a mut T>
found std::option::Option<&mut T>
为什么不可变的编译得很好,而可变的编译不好?在这种情况下,我是否缺少一些终身注释FooMut
?我已经看到很多关于生命周期和参考的答案,但我在这种情况下特别询问可变与不可变的情况。
解决方案
不可变和可变引用的生命周期方面已经在不同的地方进行了介绍:请参阅问题的注释和答案中嵌入的引用。
我在这里写了一些关注这个特定案例的笔记,希望能够阐明 Rust 生命周期的困难概念(至少对我来说这很困难)。
考虑这个片段,一个简化版本,暴露了问题的相同问题:
struct Foo<'a> {
x: &'a mut i32,
}
impl<'b> Foo<'b> {
fn x(&mut self) -> &'b mut i32 { self.x }
}
fn main() {
let y = &mut 5; // <- 'a(1)
let mut f = Foo { x: y }; // 'a(1) <- 'b(2)
println!("x is: {}", f.x()); // 'a(1) 'b(2) <- 'anonymous(3)
}
这里有三个生命周期在起作用:
'a(1)
类型的 y 值的生命周期&mut i32
'b(2)
类型的 f 值的生命周期Foo
'anonymous(3)
编译器分配给引用的生命周期,因为在方法&self
中没有分配明确的生命周期值。&self
fn x(&mut self) -> &'b i32
在文档中,生命周期泛型通常使用相同的字母进行注释:在此示例中,我已将生命周期泛型注释为struct
with和with以证明编译器生成的具体生命周期与两个不同的跨度相关联。impl
struct
'a
impl
'b
请参阅上面示例代码中的注释以获取可视化表示。
如果我们尝试编译,我们会得到:
error[E0312]: lifetime of reference outlives lifetime of borrowed content...
--> src/main.rs:6:30
|
6 | fn x(&self) -> &'b i32 { self.x }
| ^^^^^^
|
note: ...the reference is valid for the lifetime 'b as defined on the impl at 5:1...
--> src/main.rs:5:1
|
5 | impl<'b> Foo<'b> {
| ^^^^^^^^^^^^^^^^
note: ...but the borrowed content is only valid for the anonymous lifetime #1 defined on the method body at 6:5
--> src/main.rs:6:5
|
6 | fn x(&self) -> &'b i32 { self.x }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
我们看到'anonymous
生命周期更窄'b
(参见代码注释中的“近似”生命周期可视化):借用的内容self.x
不足以满足防锈安全规则。
现在很明显,解决方案应该是通过显式注释来减少生命周期,或者在省略规则的支持下更好地减少生命周期:
struct Foo<'a> {
x: &'a mut i32,
}
impl<'b> Foo<'b> {
fn x(&mut self) -> &mut i32 { self.x }
}
fn main() {
let y = &mut 5; // <- 'a
let mut f = Foo { x: y }; // 'a <- 'b
println!("x is: {}", f.x()); // 'a 'b
}
现在片段编译了,这里学到的教训应该是从这个答案复制的一个短语:
一条经验法则:不要只是到处发送垃圾邮件。只对应该相同的事物使用相同的生命周期
不可变引用
好的,但是为什么如果Foo::x
是不可变引用,编译器不会抛出错误?
简短的回答是:
如果内部引用是不可变的,则编译器可以确保不会出现内存问题来缩小生命周期范围。
相反,编译器会在内部可变性的情况下防止不同的生命周期跨度(在这种情况下防止'anonymous
!= 'b
),因为如果对 ( ) 的引用和对Foo
( &mut self
) 的引用的&mut i32
生命周期self.x
不等于可能会发生无效的内存状态。
为了帮助理解内部引用可变性和生命周期缩小可能发生的情况,请考虑这个无效的 rust 片段:
let mut my_ref: &mut i32 = &mut 1;
let mut f = Foo { x: my_ref };
{ | <--- narrowed lifetime scope
let y = &mut 5; |
f.x = y; |
} | <---
// ERROR: invoking f.x() when self.x is no more valid!
f.x();
有关更多详细信息,请参阅此答案。
推荐阅读
- php - 为什么 PhpStorm 允许使用 yield 的函数返回 null 类型?
- caching - 写在 L1 和 L2 缓存上的行是否也写在 LLC 中
- python - 尝试在 pycharm 上安装 spacy_langdetect 和 langid 没有成功
- javascript - 将时间增量和速度纳入速度/摩擦运动
- excel - 将 Excel 范围发布到 HTML 时文本被截断
- deep-linking - Branch.io - 如何拥有一个采用动态参数的快速链接?
- batch-file - 如何在 Windows 上创建批处理脚本以从 config.json 查找字符串并将其替换为多个缩小文件
- c++ - 如何使用文件系统微过滤器阻止网络共享驱动器上的文件覆盖
- google-chrome - Chrome 中 cookie 不匹配的数量
- c# - 显示重复项的返回类型