看看a simple implementation of this:
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`
要完全理解此错误,您必须考虑
值在内存中表示,当你移动那些价值观。让我们用一些假设来注释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 的生命周期是不是当一个对象是之间的时间段
创建和何时销毁!
打个比方,这样想:在一个人的一生中,他们会
居住在许多不同的地点,每个地点都有不同的地址。一个
Rust 生命周期与你的地址有关目前居住在,
不是关于你将来什么时候会死(虽然死也
更改您的地址)。每次你移动它都是相关的,因为你的
地址不再有效。
同样重要的是要注意生命周期不要更改代码;您的
代码控制生命周期,你的生命周期不控制代码。这
精辟的说法是“生命是描述性的,而不是规定性的”。
让我们用一些我们将使用的行号来注释Combined::new
突出生命周期:
{ // 0
let parent = Parent { count: 42 }; // 1
let child = Child { parent: &parent }; // 2
// 3
Combined { parent, child } // 4
} // 5
这具体寿命parent 是从 1 到 4,包括在内(我将
表示为[1,4])。 child的具体生命周期为[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 的生命周期将是
参数化不受任何限制——它可以是任何东西
来电者希望它是。这是荒谬的,因为调用者
可以指定 'static 生命周期,但没有办法满足
健康)状况。
我如何解决它?
最简单和最推荐的解决方案是不要尝试将
这些项目在同一结构中。通过这样做,您的
结构嵌套将模仿代码的生命周期。场所类型
将拥有的数据一起放入一个结构中,然后提供方法
允许您根据需要获取引用或包含引用的对象。
有一种特殊情况,生命周期跟踪过于热心:
当你有东西放在堆上时。当您使用
例如,Box<T>。在这种情况下,移动的结构
包含指向堆的指针。指向的值将保持不变
稳定,但指针本身的地址会移动。在实践中,
这并不重要,因为您始终遵循指针。
有些 crate 提供了表示这种情况的方法,但它们
要求基地址永远不动.这排除了变异
向量,这可能会导致重新分配和移动
堆分配的值。
使用 Rental 解决的问题示例:
在其他情况下,您可能希望转向某种类型的引用计数,例如使用Rc 或Arc。
更多信息
将 parent 移入结构后,为什么编译器无法获取对 parent 的新引用并将其分配给结构中的 child?
虽然理论上可以这样做,但这样做会带来大量的复杂性和开销。每次移动对象时,编译器都需要插入代码来“修复”引用。这意味着复制一个结构不再是一个非常便宜的操作,它只是移动一些位。它甚至可能意味着像这样的代码很昂贵,这取决于假设的优化器有多好:
let a = Object::new();
let b = a;
let c = b;
而不是强迫这种情况发生每一个移动,程序员得到选择通过创建仅在您调用它们时才采用适当引用的方法来实现这种情况。
引用自身的类型
有一种特殊情况,你能够创建一个引用自身的类型。不过,您需要使用 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);
}
从某种意义上说,这确实有效,但创造的价值受到高度限制——它可以绝不被移动。值得注意的是,这意味着它不能从函数返回或按值传递给任何东西。构造函数显示与上述生命周期相同的问题:
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);
}
也可以看看:
Pin 呢?
Pin,稳定在 Rust 1.33 中,有这个 in the module documentation:
这种情况的一个主要示例是构建自引用结构,因为移动带有指向自身的指针的对象会使它们无效,这可能会导致未定义的行为。
重要的是要注意“自我参照”并不一定意味着使用一个参考.事实上,example of a self-referential struct 明确表示(强调我的):
我们不能用一个普通的引用来通知编译器,
因为这种模式不能用通常的借用规则来描述。
反而我们使用原始指针,虽然已知不为空,
因为我们知道它指向字符串。
自 Rust 1.0 以来,就已经存在为这种行为使用原始指针的能力。实际上,owning-ref 和 rental 在底层使用原始指针。
Pin 添加到表中的唯一内容是声明给定值保证不会移动的常用方法。
也可以看看: