我有一个值,我想存储该值和对
在我自己的类型中该值中的某些内容:
struct Thing {
count: u32,
}
struct Combined<'a>(Thing, &'a u32);
fn make_combined<'a>() -> Combined<'a> {
let thing = Thing { count: 42 };
Combined(thing, &thing.count)
}
有时,我有一个值,我想存储该值和对
该值在同一结构中:
struct Combined<'a>(Thing, &'a Thing);
fn make_combined<'a>() -> Combined<'a> {
let thing = Thing::new();
Combined(thing, &thing)
}
有时,我什至没有引用值(value),我得到了
同样的错误:
struct Combined<'a>(Parent, Child<'a>);
fn make_combined<'a>() -> Combined<'a> {
let parent = Parent::new();
let child = parent.child();
Combined(parent, child)
}
在这些情况中的每一种情况下,我都会收到一个错误,其中一个值“做
活得不够久”。这个错误是什么意思?
最佳答案
让我们看看 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`
要完全理解此错误,您必须考虑如何值在内存中表示,移动时会发生什么
那些值(value)观。让我们用一些假设来注释
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
那样移动是,那么它将指不再保证的内存
有一个有效的值(value)。允许存储任何其他代码
内存地址 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 提供了表示这种情况的方法,但它们
要求基地址永不移动。这排除了变异
向量,这可能会导致重新分配和移动
堆分配的值。
通过租赁解决的问题示例:
在其他情况下,您可能希望转向某种类型的引用计数,例如使用
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);
}
从某种意义上说,这确实有效,但创造的值(value)受到高度限制——它永远无法移动。值得注意的是,这意味着它不能从函数返回或按值传递给任何东西。构造函数显示了与上述生命周期相同的问题: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 以来,就已经存在使用原始指针来实现这种行为的能力。事实上,拥有引用和租赁在引擎盖下使用原始指针。
Pin
添加到表中的唯一内容是声明给定值保证不会移动的常用方法。也可以看看:
关于rust - 为什么我不能在同一个结构中存储一个值和对该值的引用?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/32300132/