让我们看看:
struct Parent {
count: u32,
}
struct Child<'a> {
parent: &'a Parent,
}
struct Combined<'a> {
parent: Parent,
child: Child<'a>,
}
impl<'a> Combined<'a> {
fn new() -> Self {
let parent = Parent { count: 42 };
let child = Child { parent: &parent };
Combined { parent, child }
}
}
fn main() {}
这将失败并出现错误:
error[E0515]: cannot return value referencing local variable `parent`
--> src/main.rs:19:9
|
17 | let child = Child { parent: &parent };
| ------- `parent` is borrowed here
18 |
19 | Combined { parent, child }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function
error[E0505]: cannot move out of `parent` because it is borrowed
--> src/main.rs:19:20
|
14 | impl<'a> Combined<'a> {
| -- lifetime `'a` defined here
...
17 | let child = Child { parent: &parent };
| ------- borrow of `parent` occurs here
18 |
19 | Combined { parent, child }
| -----------^^^^^^---------
| | |
| | move out of `parent` occurs here
| returning this value requires that `parent` is borrowed for `'a`
要完全理解这个错误,你必须考虑如何
值在内存中表示,当您move这些价值观。我们来注释一下Combined::new
与一些假设
显示值所在位置的内存地址:
let parent = Parent { count: 42 };
// `parent` lives at address 0x1000 and takes up 4 bytes
// The value of `parent` is 42
let child = Child { parent: &parent };
// `child` lives at address 0x1010 and takes up 4 bytes
// The value of `child` is 0x1000
Combined { parent, child }
// The return value lives at address 0x2000 and takes up 8 bytes
// `parent` is moved to 0x2000
// `child` is ... ?
应该发生什么child
?如果该值只是像这样移动parent
是,那么它将引用不再保证的内存
其中有一个有效的值。允许存储任何其他代码段
内存地址 0x1000 处的值。访问该内存,假设它是
整数可能会导致崩溃和/或安全错误,并且是其中之一
Rust 可防止的主要错误类别。
这正是问题所在寿命防止。一生是一个
一些元数据可以让你和编译器知道一个
值将在其有效当前内存位置。那是一个
重要的区别,因为这是 Rust 新手常犯的错误。
Rust 的寿命是not对象出现之间的时间间隔
创建和销毁的时间!
打个比方,这样想:在人的一生中,他们会
驻留在许多不同的位置,每个位置都有不同的地址。 A
Rust 的生命周期与你的地址有关目前居住在,
不是关于你将来什么时候会死(尽管也会死)
更改您的地址)。每次你搬家都是相关的,因为你的
地址不再有效。
同样重要的是要注意生命周期do not更改您的代码;你的
代码控制生命周期,你的生命周期并不控制代码。这
精辟的说法是“生命是描述性的,而不是规定性的”。
我们来注释一下Combined::new
以及我们将使用的一些行号
突出显示生命周期:
{ // 0
let parent = Parent { count: 42 }; // 1
let child = Child { parent: &parent }; // 2
// 3
Combined { parent, child } // 4
} // 5
The 具体寿命 of parent
是从 1 到 4,包含这两个值(我会
表示为[1,4]
)。具体寿命child
is [2,4]
, 和
返回值的具体生命周期是[4,5]
。它是
可能有从零开始的具体生命周期 - 这将是
表示函数或其他东西的参数的生命周期
存在于街区之外。
请注意,child
本身就是[2,4]
,但它指的是
到一个生命周期为[1,4]
。这很好,只要
引用值先于被引用值失效。这
当我们尝试返回时出现问题child
来自街区。这个会
“过度延长”寿命超出其自然长度。
这个新知识应该可以解释前两个例子。第三
需要审视以下措施的实施情况Parent::child
。机会
是,它看起来像这样:
impl Parent {
fn child(&self) -> Child { /* ... */ }
}
这使用终生省略以避免写出明确的通用的
寿命参数。它相当于:
impl Parent {
fn child<'a>(&'a self) -> Child<'a> { /* ... */ }
}
在这两种情况下,该方法都表示Child
结构将是
返回已用具体生命周期参数化的self
。换句话说,Child
实例包含一个引用
到Parent
创造了它,因此不能比它活得更久Parent
实例。
这也让我们认识到,我们的行为确实出了问题。
创建函数:
fn make_combined<'a>() -> Combined<'a> { /* ... */ }
尽管您更有可能看到以不同形式编写的内容:
impl<'a> Combined<'a> {
fn new() -> Combined<'a> { /* ... */ }
}
在这两种情况下,都没有通过
争论。这意味着生命周期Combined
将
参数化 with 不受任何限制 - 它可以是任何东西
呼叫者希望如此。这是无意义的,因为调用者
可以指定'static
一生都没有办法满足
健康)状况。
我如何解决它?
最简单和最推荐的解决方案是不要尝试将
这些项目在同一结构中组合在一起。通过这样做,您的
结构嵌套将模仿代码的生命周期。场所类型
将数据一起放入一个结构中,然后提供方法
允许您根据需要获取引用或包含引用的对象。
有一种特殊情况,生命周期跟踪过于热心:
当你有东西放在堆上时。当您使用Box<T>
, 例如。在这种情况下,移动的结构
包含一个指向堆的指针。指定值将保持不变
稳定,但是指针本身的地址会移动。在实践中,
这并不重要,因为您始终遵循指针。
有些板条箱提供了表示这种情况的方法,但它们
要求基地址永远不要动。这排除了变异
向量,这可能会导致重新分配和移动
堆分配的值。
-
rental https://crates.io/crates/rental(不再维护或支持)
-
拥有_参考 https://crates.io/crates/owning_ref (has 多重稳健性问题 https://github.com/noamtashma/owning-ref-unsoundness)
- 衔尾蛇 https://crates.io/crates/ouroboros
- 自身细胞 https://crates.io/crates/self_cell
- yoke https://crates.io/crates/yoke
租赁解决的问题示例:
- String::chars 是否有自有版本? https://stackoverflow.com/q/47193584/155423
- 独立于方法返回 RWLockReadGuard https://stackoverflow.com/q/50496879/155423
- 如何在 Rust 中返回锁定结构成员的迭代器? https://stackoverflow.com/q/51664098/155423
- 如何返回对互斥体下值的子值的引用? https://stackoverflow.com/q/40095383/155423
- 如何使用支持 Futures 的超级块的 Serde 零拷贝反序列化来存储结果? https://stackoverflow.com/q/43702185/155423
- 如何存储引用而不必处理生命周期? https://stackoverflow.com/q/49300618/155423
在其他情况下,您可能希望转向某种类型的引用计数,例如使用Rc https://doc.rust-lang.org/std/rc/struct.Rc.html or Arc https://doc.rust-lang.org/std/sync/struct.Arc.html.
更多信息
搬家后parent
进入结构体,为什么编译器无法获得新的引用parent
并将其分配给child
在结构中?
虽然理论上可以做到这一点,但这样做会带来大量的复杂性和开销。每次移动对象时,编译器都需要插入代码来“修复”引用。这意味着复制结构不再是一个非常便宜的操作,只需移动一些位即可。它甚至可能意味着这样的代码很昂贵,具体取决于假设的优化器有多好:
let a = Object::new();
let b = a;
let c = b;
而不是强迫这种情况发生every移动,程序员得到choose通过创建仅在调用它们时才会采用适当引用的方法来实现这种情况。
具有自身引用的类型
有一种具体情况,您can创建一个引用自身的类型。你需要使用类似的东西Option
分两步完成:
#[derive(Debug)]
struct WhatAboutThis<'a> {
name: String,
nickname: Option<&'a str>,
}
fn main() {
let mut tricky = WhatAboutThis {
name: "Annabelle".to_string(),
nickname: None,
};
tricky.nickname = Some(&tricky.name[..4]);
println!("{:?}", tricky);
}
从某种意义上来说,这确实有效,但创造的价值受到严格限制——它可以never被移动。值得注意的是,这意味着它不能从函数返回或按值传递给任何东西。构造函数显示了与上面相同的生命周期问题:
fn creator<'a>() -> WhatAboutThis<'a> { /* ... */ }
如果您尝试使用方法执行相同的代码,您将需要诱人但最终无用的方法&'a self
。当涉及到这一点时,此代码会受到更多限制,并且在第一个方法调用后您将收到借用检查器错误:
#[derive(Debug)]
struct WhatAboutThis<'a> {
name: String,
nickname: Option<&'a str>,
}
impl<'a> WhatAboutThis<'a> {
fn tie_the_knot(&'a mut self) {
self.nickname = Some(&self.name[..4]);
}
}
fn main() {
let mut tricky = WhatAboutThis {
name: "Annabelle".to_string(),
nickname: None,
};
tricky.tie_the_knot();
// cannot borrow `tricky` as immutable because it is also borrowed as mutable
// println!("{:?}", tricky);
}
也可以看看:
- 在一个代码中不能一次多次借用可变代码 - 但可以在另一个非常相似的代码中借用可变的 https://stackoverflow.com/q/31067031/155423
关于什么Pin
?
Pin https://doc.rust-lang.org/std/pin/struct.Pin.html,在 Rust 1.33 中稳定,有这个在模块文档中 https://doc.rust-lang.org/std/pin/index.html:
这种情况的一个主要例子是构建自引用结构,因为移动带有指向自身的指针的对象将使它们无效,这可能会导致未定义的行为。
重要的是要注意“自我引用”并不一定意味着使用一个参考。确实,自引用结构的示例 https://doc.rust-lang.org/std/pin/index.html#example-self-referential-struct具体说(强调我的):
我们无法通过普通引用通知编译器,
因为这种模式无法用通常的借用规则来描述。
反而我们使用原始指针,尽管已知它不为空,
因为我们知道它指向字符串。
自 Rust 1.0 以来就已经存在使用原始指针来实现此行为的能力。事实上,拥有引用和租赁在幕后使用原始指针。
唯一的一点就是Pin
添加到表中是声明给定值保证不会移动的常用方法。
也可以看看:
- 如何将 Pin 结构与自引用结构一起使用? https://stackoverflow.com/q/49860149/155423