为什么不能在同一结构中存储值和对该值的引用? [英] Why can't I store a value and a reference to that value in the same struct?
问题描述
我有一个值,我想存储该值和对 在我自己的类型的那个值里面的东西:
I have a value and I want to store that value and a reference to something inside that value in my own type:
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)
}
有时候,我有一个值,我想存储该值和对 该值具有相同的结构:
Sometimes, I have a value and I want to store that value and a reference to that value in the same structure:
struct Combined<'a>(Thing, &'a Thing);
fn make_combined<'a>() -> Combined<'a> {
let thing = Thing::new();
Combined(thing, &thing)
}
有时候,我什至没有参考值,但我得到了 相同的错误:
Sometimes, I'm not even taking a reference of the value and I get the same error:
struct Combined<'a>(Parent, Child<'a>);
fn make_combined<'a>() -> Combined<'a> {
let parent = Parent::new();
let child = parent.child();
Combined(parent, child)
}
在每种情况下,我都会收到一个错误,指出其中一个值确实 寿命不足".此错误是什么意思?
In each of these cases, I get an error that one of the values "does not live long enough". What does this error mean?
推荐答案
让我们看看这将失败,并显示以下错误:
This will fail with the error:
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
显示值所在位置的内存地址:
To completely understand this error, you have to think about how the
values are represented in memory and what happens when you move
those values. Let's annotate Combined::new
with some hypothetical
memory addresses that show where values are located:
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可以防止的主要错误类别.
What should happen to child
? If the value was just moved like parent
was, then it would refer to memory that no longer is guaranteed to
have a valid value in it. Any other piece of code is allowed to store
values at memory address 0x1000. Accessing that memory assuming it was
an integer could lead to crashes and/or security bugs, and is one of
the main categories of errors that Rust prevents.
这正是寿命可以防止的问题.一生是一个 一点元数据,使您和编译器可以知道 值将在其当前存储位置上有效.那是 重要区别,因为这是Rust新移民常犯的错误. 锈蚀寿命不是对象之间的时间间隔 创建并销毁它!
This is exactly the problem that lifetimes prevent. A lifetime is a bit of metadata that allows you and the compiler to know how long a value will be valid at its current memory location. That's an important distinction, as it's a common mistake Rust newcomers make. Rust lifetimes are not the time period between when an object is created and when it is destroyed!
打个比方,这样想:在一个人的一生中,他们会 居住在许多不同的位置,每个位置都有不同的地址.一种 Rust的生存期与您当前居住的地址有关, 不是关于你将来何时会死(尽管也会死) 更改您的地址).每次移动都非常重要,因为您的 地址不再有效.
As an analogy, think of it this way: During a person's life, they will reside in many different locations, each with a distinct address. A Rust lifetime is concerned with the address you currently reside at, not about whenever you will die in the future (although dying also changes your address). Every time you move it's relevant because your address is no longer valid.
同样重要的是要注意生命周期不要更改您的代码;你的 代码控制生命周期,而您的生命周期则无法控制代码.这 俗话说的是:生命是描述性的,而不是描述性的".
It's also important to note that lifetimes do not change your code; your code controls the lifetimes, your lifetimes don't control the code. The pithy saying is "lifetimes are descriptive, not prescriptive".
让我们用一些行号注释Combined::new
突出寿命:
Let's annotate Combined::new
with some line numbers which we will use
to highlight lifetimes:
{ // 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]
.它是
可能有从零开始的具体寿命-那会
表示函数或某项参数的生命周期
存在于街区之外.
The concrete lifetime of parent
is from 1 to 4, inclusive (which I'll
represent as [1,4]
). The concrete lifetime of child
is [2,4]
, and
the concrete lifetime of the return value is [4,5]
. It's
possible to have concrete lifetimes that start at zero - that would
represent the lifetime of a parameter to a function or something that
existed outside of the block.
请注意,child
本身的生存期为[2,4]
,但指的是
为有效期为[1,4]
的值.只要
引用值在引用值之前无效.这
当我们尝试从块中返回child
时,会发生问题.这个会
寿命超出"自然寿命.
Note that the lifetime of child
itself is [2,4]
, but that it refers
to a value with a lifetime of [1,4]
. This is fine as long as the
referring value becomes invalid before the referred-to value does. The
problem occurs when we try to return child
from the block. This would
"over-extend" the lifetime beyond its natural length.
此新知识应解释前两个示例.第三
一个需要研究Parent::child
的实现.机会
是的,它看起来像这样:
This new knowledge should explain the first two examples. The third
one requires looking at the implementation of Parent::child
. Chances
are, it will look something like this:
impl Parent {
fn child(&self) -> Child { /* ... */ }
}
这使用了终生省略来避免编写显式的泛型 寿命参数.等效于:
This uses lifetime elision to avoid writing explicit generic lifetime parameters. It is equivalent to:
impl Parent {
fn child<'a>(&'a self) -> Child<'a> { /* ... */ }
}
在两种情况下,该方法都说Child
结构将是
返回的参数已使用的具体寿命进行了参数化
self
.换句话说,Child
实例包含一个引用
到创建它的Parent
,因此寿命不能超过
Parent
实例.
In both cases, the method says that a Child
structure will be
returned that has been parameterized with the concrete lifetime of
self
. Said another way, the Child
instance contains a reference
to the Parent
that created it, and thus cannot live longer than that
Parent
instance.
这也使我们认识到我们的确存在问题 创建功能:
This also lets us recognize that something is really wrong with our creation function:
fn make_combined<'a>() -> Combined<'a> { /* ... */ }
尽管您更有可能看到用其他形式写的内容:
Although you are more likely to see this written in a different form:
impl<'a> Combined<'a> {
fn new() -> Combined<'a> { /* ... */ }
}
在两种情况下,都不会通过
争论.这意味着Combined
的生存期为
参数化为不受任何约束-可以是任何约束
呼叫者希望它成为.这是荒谬的,因为调用者
可以指定'static
的生存期,无法满足要求
条件.
In both cases, there is no lifetime parameter being provided via an
argument. This means that the lifetime that Combined
will be
parameterized with isn't constrained by anything - it can be whatever
the caller wants it to be. This is nonsensical, because the caller
could specify the 'static
lifetime and there's no way to meet that
condition.
最简单,最推荐的解决方案是不要尝试放置 这些项目以相同的结构在一起.这样,您的 结构嵌套将模仿代码的生命周期.地点类型 将数据一起拥有到一个结构中,然后提供方法 允许您根据需要获取引用或包含引用的对象.
The easiest and most recommended solution is to not attempt to put these items in the same structure together. By doing this, your structure nesting will mimic the lifetimes of your code. Place types that own data into a structure together and then provide methods that allow you to get references or objects containing references as needed.
在一种特殊情况下,寿命跟踪过分热心:
当您将某些东西放在堆上时.当您使用
例如,Box<T>
.在这种情况下,移动的结构
包含一个指向堆的指针.指向的值将保持不变
稳定,但指针本身的地址将移动.在实践中,
没关系,因为您总是跟随指针.
There is a special case where the lifetime tracking is overzealous:
when you have something placed on the heap. This occurs when you use a
Box<T>
, for example. In this case, the structure that is moved
contains a pointer into the heap. The pointed-at value will remain
stable, but the address of the pointer itself will move. In practice,
this doesn't matter, as you always follow the pointer.
租金箱或
The rental crate or the owning_ref crate are ways of representing this case, but they require that the base address never move. This rules out mutating vectors, which may cause a reallocation and a move of the heap-allocated values.
使用Rental解决的问题示例:
Examples of problems solved with Rental:
- 是否拥有String :: chars的自有版本?
- 独立于方法返回RWLockReadGuard
- 如何在Rust中通过锁定的struct成员返回迭代器?
- 如何返回对互斥锁下的值的子值的引用?
- 如何使用启用期货的超级块的Serde零拷贝反序列化来存储结果? >
- 如何在无需处理生命周期的情况下存储引用?
- Is there an owned version of String::chars?
- Returning a RWLockReadGuard independently from a method
- How can I return an iterator over a locked struct member in Rust?
- How to return a reference to a sub-value of a value that is under a mutex?
- How do I store a result using Serde Zero-copy deserialization of a Futures-enabled Hyper Chunk?
- How to store a reference without having to deal with lifetimes?
在其他情况下,您可能希望转到某种类型的引用计数,例如使用
In other cases, you may wish to move to some type of reference-counting, such as by using Rc
or Arc
.
将
parent
移入结构后,为什么编译器无法获取对parent
的新引用并将其分配给该结构中的child
?
After moving
parent
into the struct, why is the compiler not able to get a new reference toparent
and assign it tochild
in the struct?
虽然理论上可以做到这一点,但这样做会带来大量的复杂性和开销.每次移动对象时,编译器都需要插入代码以固定"引用.这将意味着复制结构不再是仅需移动一些位的非常便宜的操作.甚至可能意味着这样的代码很昂贵,具体取决于假设的优化器的质量:
While it is theoretically possible to do this, doing so would introduce a large amount of complexity and overhead. Every time that the object is moved, the compiler would need to insert code to "fix up" the reference. This would mean that copying a struct is no longer a very cheap operation that just moves some bits around. It could even mean that code like this is expensive, depending on how good a hypothetical optimizer would be:
let a = Object::new();
let b = a;
let c = b;
程序员不会强迫每次移动都会发生这种情况,而是通过创建仅在调用它们时才采用适当引用的方法来选择何时发生这种情况. .
Instead of forcing this to happen for every move, the programmer gets to choose when this will happen by creating methods that will take the appropriate references only when you call them.
在一种特定情况下,您可以创建一个引用其自身的类型.不过,您需要使用类似Option
的方法来分两步进行操作:
There's one specific case where you can create a type with a reference to itself. You need to use something like Option
to make it in two steps though:
#[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);
}
从某种意义上说,它确实有效,但是所创建的值受到严格限制-永远不能移动.值得注意的是,这意味着它不能从函数返回或按值传递给任何对象.构造函数在寿命方面显示出与上述相同的问题:
This does work, in some sense, but the created value is highly restricted - it can never be moved. Notably, this means it cannot be returned from a function or passed by-value to anything. A constructor function shows the same problem with the lifetimes as above:
fn creator<'a>() -> WhatAboutThis<'a> { /* ... */ }
Pin
怎么样?
已在Rust 1.33中稳定的 Pin
模块文档中的 :
What about Pin
?
Pin
, stabilized in Rust 1.33, has this in the module documentation:
这种情况的主要示例是构建自引用结构,因为移动带有指向自身的指针的对象会使它们无效,这可能导致未定义的行为.
A prime example of such a scenario would be building self-referential structs, since moving an object with pointers to itself will invalidate them, which could cause undefined behavior.
请务必注意,自我引用"并不一定意味着使用引用.实际上,自引用结构的示例具体说(强调我的意思):
It's important to note that "self-referential" doesn't necessarily mean using a reference. Indeed, the example of a self-referential struct specifically says (emphasis mine):
我们无法通过正常参考将其告知编译器, 因为这种模式无法用通常的借用规则来描述. 相反,我们使用原始指针,尽管已知该指针不为空, 因为我们知道它指向字符串.
We cannot inform the compiler about that with a normal reference, since this pattern cannot be described with the usual borrowing rules. Instead we use a raw pointer, though one which is known to not be null, since we know it's pointing at the string.
自Rust 1.0起,就已经存在使用原始指针进行此行为的功能.实际上,拥有引用和租用在幕后使用了原始指针.
The ability to use a raw pointer for this behavior has existed since Rust 1.0. Indeed, owning-ref and rental use raw pointers under the hood.
Pin
添加到表中的唯一一件事是声明保证给定值不会移动的常用方法.
The only thing that Pin
adds to the table is a common way to state that a given value is guaranteed to not move.
另请参阅:
这篇关于为什么不能在同一结构中存储值和对该值的引用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!