让我们来看一个简单的实现:
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 ... ?
孩子该怎么办?如果值只是像parent一样移动
是吗,那么它会指的记忆,不再是保证吗
其中有一个有效值。允许存储任何其他代码段
内存地址0x1000的值。访问内存,假设它是
整数可能导致崩溃和/或安全漏洞,并且是其中之一
Rust可以防止的主要错误类别。
这正是我们一生所能避免的问题。一生就是一个
一个元数据,允许你和编译器知道a的长度
值在其当前内存位置有效。这是一个
重要的区别,因为这是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结构将被删除
的具体生命周期参数化后返回的
自我。换句话说,Child实例包含一个引用
归于创造它的“父”,因此它不能活得更久
父类实例。
这也让我们认识到,我们的确有问题
创建功能:
fn make_combined<'a>() -> Combined<'a> { /* ... */ }
尽管你更可能看到它以另一种形式写出来:
impl<'a> Combined<'a> {
fn new() -> Combined<'a> { /* ... */ }
}
在这两种情况下,都没有通过
论点。这意味着组合的生命周期将是
Parameterized with不受任何约束-它可以是任何东西
调用者希望它是。这是荒谬的,因为调用者
可以指定“静态生命周期”,但没有办法满足它
条件。
我该怎么解决呢?
最简单和最推荐的解决方案是不尝试放
这些项目在同一结构中一起。这样做,你的
结构嵌套将模拟代码的生命周期。地方类型
一起拥有数据到一个结构中,然后提供方法
允许您根据需要获取包含引用的引用或对象。
有一个特殊的情况,生命周期跟踪是过度热心的:
当你在堆上放了东西。当您使用
例如,Box<T>。在这种情况下,是被移动的结构
包含指向堆的指针。指向值将保持不变
稳定,但是指针本身的地址会移动。在实践中,
这无关紧要,因为您始终遵循指针。
一些板条箱提供了表示这种情况的方法,但他们
要求基址永不移动。这就排除了变异的可能性
向量,这可能导致重新分配和移动
堆上分配值。
租金(不再维持或支持)
Owning_ref(有多个可靠性问题)
大毒蛇
self_cell
租赁解决问题的例子:
是否有String::chars的专有版本?
从方法中独立返回RWLockReadGuard
我如何在Rust中返回一个锁定结构成员的迭代器?
如何返回对互斥量下的值的子值的引用?
我如何存储一个结果使用Serde零拷贝反序列化的未来启用超块?
如何存储一个引用而不必处理生命周期?
在其他情况下,您可能希望使用某种类型的引用计数,例如使用Rc或Arc。
更多的信息
移动父结构后,为什么编译器不能得到一个新的引用的父和分配给子结构?
虽然理论上可以这样做,但这样做会带来大量的复杂性和开销。每次移动对象时,编译器都需要插入代码来“修复”引用。这将意味着复制结构体不再是一个非常廉价的操作,只是移动一些位。它甚至可能意味着这样的代码是昂贵的,这取决于一个假设的优化器有多好:
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> { /* ... */ }
如果你试图用一个方法来做同样的代码,你将需要一个诱人但最终毫无用处的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呢?
在Rust 1.33中稳定的引脚,在模块文档中有这个:
这种场景的一个主要例子是构建自引用结构,因为移动带有指向自身的指针的对象将使它们失效,这可能导致未定义的行为。
需要注意的是,“自我参照”并不一定意味着使用参照。事实上,一个自引用结构体的例子明确地说(强调我的):
我们不能用正常的引用通知编译器,
因为这种模式不能用通常的借款规则来描述。
相反,我们使用一个原始指针,尽管已知它不是空指针,
因为我们知道它指向弦。
从Rust 1.0开始,就有了使用原始指针的能力。实际上,ownership -ref和rental在底层使用原始指针。
Pin向表中添加的唯一一件事是声明给定值保证不移动的常用方法。
参见:
如何使用自引用结构的针结构?