“这是一个可爱的星期日下午,当我决定做一些有趣的生锈测验,但是在解释答案时,一个微妙的错误最终在研究数小时和公关中。
And after learning so much about the niche Rust topic of temporaries, I'm determined to also burden you with this unholy knowledge, if you're just willing to read a few thousand words on it.
在了解了有关临时性的利基生锈主题之后,如果您只是愿意在上面阅读几千个单词,我也决心为您负担这种邪恶的知识。
Spoiler alert! While I'll not talk about the exact problem in Quiz #37 I'll get pretty close to it. So you might want to try the quiz for yourself before. And if you want to know what used to be wrong about the explanation to that problem before here's my PR
扰流板警报!虽然我不会谈论测验#37中的确切问题,但我会非常接近它。因此,您可能以前想自己尝试测验。如果您想知道在我的公关之前,关于该问题的解释曾经有什么问题
If you've been programming in Rust for a while you must have seen this term used in an error like:
如果您在Rust中编程了一段时间,则一定已经在错误的错误中看到了此术语:
error[E0716]: temporary value dropped while borrowed
错误[E0716]:借用时临时值下降
So, what if I told you temporary values don't exists?
那么,如果我告诉您临时值不存在怎么办?
Got your attention? Good.
引起了您的注意?好的。
This is, of course, too clickbaity. It's just a mismatch between how the term is used by the compiler errors and how it's defined in the reference.
当然,这也是Clickbaity。这只是编译器错误的术语如何使用的术语与参考中的定义方式之间的不匹配。
But don't worry about reading the reference just yet; we will untangle that definition in a moment, so now let's dive into it.
但是不必担心暂时阅读参考文献;我们将在稍后解开该定义,因此现在让我们深入研究。
Take a look at this example.
看看这个示例。
struct Foo; impl Drop for Foo { fn drop ( & mut self ) { print!("foo" ); } } fn main () { let x = Some( & Foo); x; }
结构foo;for for fOO {fn drop(&mut self){print!(“ foo”);}} fn main(){让x = some(&foo);x;}
When we try to compile it, we get this error.
当我们尝试编译它时,我们会收到此错误。
error[E0716]: temporary value dropped while borrowed --> src/main.rs:9:23 | 9 | let x = Some(&Foo); | ^^^ - temporary value is freed at the end of this statement | | | creates a temporary value which is freed while still in use 10 | x; | - borrow later used here | help: consider using a `let` binding to create a longer lived value | 9 ~ let binding = Foo; 10 ~ let x = Some(&binding); | For more information about this error, try `rustc --explain E0716`.
错误[E0716]:借用 - > src/main.rs:9:23 |时,临时值下降了9 |令x =一些(&foo);|^^^ - 临时值在此语句的结尾释放|||创建一个暂时值,该价值在仍在使用的同时被释放10 |x;|- 后来在这里使用|帮助:考虑使用“让'绑定”来创建更长的寿命|9〜LET binding = foo;10〜令x =一些(&绑定);|有关此错误的更多信息,请尝试“ Rustc - 解释E0716”。
Somehow, when we declare Foo in let x = Some(&Foo) there's really no variable to hold it. So, it's created temporarily, living only until the end of the let statement. After that, it's dropped, so when we try to invoke x again, it'd contain a dangling reference, so the compiler disallows it.
不知何故,当我们在让x =某些(&foo)中声明foo时,实际上没有变量可以容纳它。因此,它是暂时创建的,只有直到列出陈述结束为止。之后,它被删除了,因此当我们尝试再次调用X时,它会包含一个悬空的参考,因此编译器将其取消。
Does this make sense? No? Kinda? Maybe we should just get the Some out of the way, it doesn't seem to be relevant for this discussion, and it shouldn't change anything.
这有意义吗?不?有点?也许我们应该把一些内容弄清楚,它似乎与此讨论无关,并且不应该改变任何事情。
So now we can rewrite it like this.
因此,现在我们可以这样重写。
struct Foo; impl Drop for Foo { fn drop ( & mut self ) { print!("foo" ); } } fn main () { let x = & Foo; x; }
结构foo;for for fOO {fn drop(&mut self){print!(“ foo”);}} fn main(){让x =&foo;x;}
Now that we have less noise, the error is a bit clearer— wait, this compiles!
既然我们的噪音较少,那么错误就更清楚了 - 等等,这会编译!
Okay, okay, let's go back to the previous version, as it seems like there's no temporary value in this one.
好的,好吧,让我们回到上一个版本,因为这个版本似乎没有临时值。
So we had something like this, right?
所以我们有这样的东西,对吗?
struct Foo; fn main () { let x = Some( & Foo); x; }
结构foo;fn main(){令x = some(&foo);x;}
And the error was... wait, I forgot the Drop impl for Foo . Well, it shouldn't matter as the Drop only printed"foo" . The error was—
错误是...等等,我忘记了foo for for for for for。好吧,这无关紧要,因为滴只只打印了“ foo”。错误是 -
Wait this compiles...
等待此编译...
Now I better tell you what's really going on here.
现在,我最好告诉您这里发生了什么。
Let's begin with the beginning. The reference manual defines temporaries as:
让我们从头开始。参考手册将临时性定义为:
When using a value expression in most place expression contexts, a temporary unnamed memory location is created and initialized to that value. The expression evaluates to that location instead, except if promoted to a static. The drop scope of the temporary is usually the end of the enclosing statement.
在大多数位置表达式上下文中使用值表达式时,会创建一个临时的未命名内存位置并初始化为该值。表达式评估到该位置,除非升为静态。临时范围的下降范围通常是封闭语句的末尾。
Okay, that definition is confusing, let's zoom in on the first part.
好的,这个定义令人困惑,让我们放大第一部分。
"When using a value expression in most place expression contexts, a temporary unnamed memory location is created and initialized to that value"
“当在大多数位置表达式上下文中使用值表达式时,创建并初始化为该值的临时未命名的内存位置”
The first thing to notice is that, under some condition — which we will explore later — a temporary is created. The temporary is defined as a memory location and not a value, that's why I've been so insistent on calling them"temporaries" instead of"temporary values". That's the mismatch in the wording between the reference and the errors that can lead to (at least my) confusion.
要注意的第一件事是,在某种情况下(我们将在以后探索)创建临时性。临时性定义为内存位置而不是值,这就是为什么我如此坚持称其为“临时性”而不是“临时值”。这就是参考文字和可能导致(至少我的)混乱的错误之间的措辞不匹配。
So when an expression triggers the creation of a temporary, its value is placed there. What makes this memory location special is that it's unnamed, meaning that, in contrast to the memory location of a variable that's declared in a statement like let x = ... , there's no associated alias such as x .
因此,当表达式触发临时性的创建时,其值就将其放置在那里。使此内存位置与众不同的原因是它的命名是,这意味着,与在诸如Let x = ...之类的语句中声明的变量的内存位置相反,没有相关的别名,例如x。
Now let's explore what the conditions are that trigger the creation of a temporary.
现在,让我们探索触发临时性创建的条件。
In the definition, it mentions that a temporary is created when a value expression is put in a place expression context. To understand this, we must understand what value expressions, place expressions, and place expression context are. Luckily these are all defined in the same section.
在定义中,它提到将值表达式放在位置表达上下文中时会创建临时性。要理解这一点,我们必须了解哪些价值表达式,放置表达式和位置表达上下文是。幸运的是,这些都是在同一部分中定义的。
First, let's look at the definition of a place expression.
首先,让我们看一下位置表达式的定义。
A place expression is an expression that represents a memory location. These expressions are paths which refer to local variables, static variables, dereferences (*expr), array indexing expressions (expr[expr]), field references (expr.f) and parenthesized place expressions.
位置表达式是代表内存位置的表达式。这些表达式是指局部变量,静态变量,dereference(*expr),阵列索引表达式(expr [expr]),字段引用(expr.f)和括号的位置表达式。
The key here is that a place expression represents a memory location. The simplest case given is a variable. For example, in the statement let x = y; if y is a local variable, it's a place expression. Another case is an expression involving the dereference operator ( * ), like *x in if (*x + 2) > 0 {...} . This makes sense since it should represent a value somewhere in memory that's being dereferenced; One important thing to notice is that while *x is a place expression, (*x + 2) isn't.
这里的关键是位置表达式代表内存位置。给出的最简单情况是一个变量。例如,在语句中,令x = y;如果y是局部变量,则是一个位置表达式。另一种情况是涉及解除操作员( *)的表达式,例如( *x + 2)> 0 {...}中的 *x。这是有道理的,因为它应该代表记忆中某个地方的值。要注意的一件重要的事情是,尽管 *x是一个位置表达式,但( *x + 2)不是。
And value expressions are everything else. Basically, expressions that represent an actual value, such as 1 , x + 2 ,"hello" , Vec::new() , or RangeFull . Notice that a value expression can contain a place expression such as *x + 2 but the expression itself still represents the value of the operation.
价值表达是其他一切。基本上,代表实际值的表达式,例如1,x + 2,“ Hello”,Vec :: new()或rangefull。请注意,值表达式可以包含一个位置表达式,例如 *x + 2,但表达式本身仍然表示操作的值。
Finally, a"place expression context" is a context where one can expect a place expression. It's explained here, and it presents an exhaustive list of place expression contexts. The most important for our examples are the operand of a reference operator ( & ), the left-hand side of an assignment like x = 5 , and the initializer of a let statement. The latter being the right-hand side of such a statement, for example, 5 in let x = 5; . The definition lists quite a few other cases, such as the x in a match x { ... } or in if let ... = x { ... } , but just know the same rules about temporaries apply in those cases.
最后,“位置表达上下文”是一个可以期待位置表达式的上下文。它在这里进行了解释,并提供了详尽的位置表达环境列表。对于我们的示例,最重要的是参考操作员(&)的操作数,X = 5(例如X = 5)的左侧以及Let语句的初始化器。后者是这种说法的右侧,例如,在let x = 5中5;。该定义列出了许多其他情况,例如匹配x {...}中的X或IF Let ... = x {...}中的X,但只知道在这些情况下适用有关临时性的相同规则。
Phew, still with me? So just one last time, to drive it home.
ph,还在和我在一起吗?因此,最后一次将其带回家。
Place expressions are expressions that represent a location in memory. Value expressions are every other expression, these represent values. Contexts where place expressions are expected are called"place expression contexts," which are, among others, the right-hand side of a let statement or the operand of a reference operator ( & ).
位置表达式是表示内存中位置的表达式。值表达式是其他所有表达式,这些表示值。预期位置表达式的上下文称为“位置表达上下文”,其中包括LET语句或参考操作员(&)的操作数的右侧。
With this in mind, when value expressions are used in place expression contexts, a temporary memory location is created, and the value of the expression is placed there. The expression then represents the temporary memory location.
考虑到这一点,当在表达式上下文中使用值表达式时,会创建一个临时的内存位置,并将表达式的值放置在此处。然后,该表达式代表临时内存位置。
Let's see one of the simplest examples of temporaries.
让我们看看临时最简单的例子之一。
struct Foo; fn main () { let x = Foo; }
结构foo;fn main(){让x = foo;}
Since Foo is a value expression, and it's in a place expression context, the right-hand side of a let statement, it creates a temporary memory location. Foo is then inserted in the temporary, and the value in there is returned.
由于foo是一个价值表达式,并且它位于位置表达式上下文中,因此let语句的右侧会创建一个临时的内存位置。然后将foo插入临时,并返回其中的值。
Let's look at another simple example.
让我们看看另一个简单的示例。
struct Foo; fn main () { & Foo; }
结构foo;fn main(){&foo;}
Again, since Foo is a value expression in a place expression context, a temporary memory location is created. In this case, the place expression context is the operand of a & operator. Foo is then placed in the temporary, and now &Foo represents a reference to that anonymous memory location.
同样,由于FOO是位置表达式上下文中的值表达式,因此创建了一个临时的内存位置。在这种情况下,位置表达式上下文是A&运算符的操作数。然后将foo放置在临时性中,现在&foo代表对该匿名内存位置的引用。
Going back to the previous example, you can still do this and still have the program compile.
回到上一个示例,您仍然可以执行此操作,并且仍然具有编译程序。
struct Foo; fn main () { let x = Foo; x; }
结构foo;fn main(){让x = foo;x;}
You can even do this and have the program still compile!
您甚至可以做到这一点,并使程序仍然编译!
struct Foo; fn main () { let x = & Foo; x; }
结构foo;fn main(){让x =&foo;x;}
So, the question is, how temporary is a temporary really? That's to say, we haven't really talked about how long they live.
因此,问题是,暂时的临时性有多临时?就是说,我们并没有真正谈论过它们的寿命。
Now we have the neccesary definitions to talk about what makes temporaries temporary. Well, not exactly, temporaries are just temporaries because they are anonymous, meaning there's no alias associated with that memory location. It has nothing to do with their lifetime. But you could ask, since there's no associated alias, then what's their lifetime? And you would be asking all the right questions.
现在,我们有必要的定义来谈论是什么使临时工临时。好吧,不完全是临时性只是临时性,因为它们是匿名的,这意味着与该内存位置没有别名。这与他们的一生无关。但是您可以问,因为没有相关的别名,那么他们的一生是什么?您会问所有正确的问题。
Let's go to the relevant part of the definition of a temporary.
让我们转到临时定义的相关部分。
The drop scope of the temporary is usually the end of the enclosing statement.
临时范围的下降范围通常是封闭语句的末尾。
So, this is pretty clear, the drop scope of a temporary is the end of enclosing statement to be more precise we can go to this part of the reference where it tells us.
因此,这很明显,临时性的下降范围是封闭陈述的结束,更确切地说是我们可以转到它告诉我们的参考的这一部分。
Apart from lifetime extension, the temporary scope of an expression is the smallest scope that contains the expression and is one of the following: The entire function.
除了寿命扩展外,表达式的临时范围是包含表达式的最小范围,是以下内容之一:整个函数。
A statement.
一个声明。
The body of an if, while or loop expression.
if的身体,或循环表达。
The else block of an if expression.
if表达式的其他块。
The non-pattern matching condition expression of an if or while expression, or a match guard.
if或while表达式的非图案匹配条件表达式或匹配护罩。
The body expression for a match arm.
匹配臂的身体表情。
Each operand of a lazy boolean expression.
懒惰布尔表达的每个操作数。
The pattern-matching condition(s) and consequent body of if (destructors.scope.temporary.edition2024).
IF(Destructors.scope.temporary.Edition2024)的图案匹配条件和随之而来的正文。
The entirety of the tail expression of a block (destructors.scope.temporary.edition2024).
块的整个尾巴表达(destructors.scope.temporary.edition2024)。
We'll focus on"a statement," but keep in mind that its lifetime could also be any of these expressions.
我们将重点关注“陈述”,但请记住,它的一生也可能是这些表达中的任何一种。
As expected, a temporary is dropped when it goes out of its drop scope. So let's go back to the first example, where we saw the"temporary value dropped while still in use" error.
正如预期的那样,临时性在掉落范围时会掉落。因此,让我们回到第一个示例,在那里我们看到“临时值在仍在使用的同时下降”错误。
struct Foo; impl Drop for Foo { fn drop ( & mut self ) {} } fn main () { let x = Some( & Foo); x; }
结构foo;for for foo {fn drop(&mut self){}} fn main(){让x = some(&foo);x;}
And the full error was
全部错误是
error[E0716]: temporary value dropped while borrowed --> src/main.rs:7:19 | 7 | let x = Some(&Foo); | ^^^ - temporary value is freed at the end of this statement | | | creates a temporary value which is freed while still in use 8 | x; | - borrow later used here | help: consider using a `let` binding to create a longer lived value | 7 ~ let binding = Foo; 8 ~ let x = Some(&binding); |
错误[E0716]:借用 - > src/main.rs:7:19 |时,临时值下降了7 |令x =一些(&foo);|^^^ - 临时值在此语句的结尾释放|||创建一个临时值,该价值仍在使用时释放8 |x;|- 后来在这里使用|帮助:考虑使用“让'绑定”来创建更长的寿命|7〜LET binding = foo;8〜令x =一些(&绑定);|
Now we can understand this! Since the operand to & is a place expression context, and Foo is a value expression, a temporary memory location is created where the value Foo lives. The expression &Foo then returns a reference to that temporary, which drop scope is the end of the let statement, as the error mentions. After which the temporary memory location is freed, dropping Foo ; and since we still use x on line 8, the compiler complains, as otherwise it'd allow a dangling reference.
现在我们可以理解这一点!由于操作数到&是一个位置表达式上下文,而foo是一个价值表达式,因此在值foo持有的位置创建了一个临时的内存位置。然后,表达式&foo返回对该临时性的引用,该临时范围是LET语句的末尾,如错误所述。之后,释放了临时内存位置,放弃了foo;而且由于我们仍然在第8行上使用X,因此编译器抱怨,否则它允许悬挂参考。
Good! But then why is this allowed?
好的!但是为什么允许这呢?
struct Foo; impl Drop for Foo { fn drop ( & mut self ) {} } fn main () { let x = Foo; x; }
结构foo;for for foo {fn drop(&mut self){}} fn main(){让x = foo;x;}
Luckily, this case is not complicated. The initializer (the right-hand side) of a let statement is a place expression context, and since Foo is a value expression, a temporary memory location is created where the value is held. But values can always be moved out from temporaries , so Foo is moved into x , which is a variable with a scope of the whole main function; therefore, it's still alive for the next line, and this program is valid!
幸运的是,这种情况并不复杂。LET语句的初始化程序(右侧)是一个位置表达式上下文,并且由于FOO是值表达式,因此在保存值的位置创建了临时存储位置。但是值总是可以从临时性中移出,因此foo被移至x,这是一个具有整个主函数范围的变量。因此,下一行仍然还活着,并且该程序是有效的!
Okay, good, but then explain this! (It compiles)
好的,很好,但是请解释一下!(编译)
struct Foo; fn main () { let x = Some( & Foo); x; }
结构foo;fn main(){令x = some(&foo);x;}
Very well, the definition of temporaries mentioned this:
很好,临时工的定义提到了这一点:
The expression evaluates to that location instead, except if promoted to a static.
表达式评估到该位置,除非升为静态。
So what does that mean? Again, going to the reference
那是什么意思?再次参考
Promotion of a value expression to a 'static slot occurs when the expression could be written in a constant and borrowed, and that borrow could be dereferenced where the expression was originally written, without changing the runtime behavior. That is, the promoted expression can be evaluated at compile-time and the resulting value does not contain interior mutability or destructors (these properties are determined based on the value where possible, e.g. &None always has the type &'static Option<_>, as it contains nothing disallowed).
当表达式可以以常数和借用的形式写入时,就会将价值表达式促进静态插槽,并且可以在最初编写表达式的情况下借用借用,而无需更改运行时行为。也就是说,可以在编译时评估促进的表达式,并且结果值不包含内部突变性或破坏者(这些属性是根据可能的值确定的,例如,例如,没有任何东西都具有类型&'static Option <_>,因为它包含任何不禁止的)。
It's a bit wordy, so let's break it down. When a value expression is promoted, it means that instead of placing it into a temporary memory slot, it's stored in memory with a 'static lifetime, like a const or static variable would be. So if something like let x = Some(&Foo) meets the const promotion condition, &Foo would return a reference to an 'static memory location instead of a temporary memory location that would go out of scope at the end of the statement.
有点言语,所以让我们分解。当促进值表达式时,这意味着它没有将其放入临时的内存插槽中,而是将其存储在具有'静态寿命的内存中,例如const或static变量。因此,如果让x =某些(&foo)之类的东西符合const促进条件,&foo将返回对'静态内存位置的引用,而不是在语句结束时范围内的临时内存位置。
The condition for this to happen is that, at runtime, there's no change in behavior between returning the temporary memory location and borrowing and dereferencing the 'static location in its place instead. Or to put it another way, we can replace the expression
发生这种情况的条件是,在运行时,返回临时内存位置与借用和取消其位置的静态位置之间的行为没有变化。或换句话说,我们可以用以下程序替换表达式
const P =
const p =
Notice how only runtime behavior is mentioned, compile-time behavior could be affected as promotion can make some programs valid that wouldn't be otherwise.
请注意,仅提及运行时行为,汇编时间行为可能会受到影响,因为促销可以使某些程序有效,而这些程序将不可能。
The reference tells us exactly when this condition is met, the following 3 things need to be true:
参考会告诉我们何时满足这种情况,以下三件事必须为真:
The expression can be evaluated at compile time: Otherwise it'd be impossible to place in a constant.
可以在编译时评估该表达式:否则将不可能以常数放置。
The expression has no destructors, basically no Drop implementation invoked for the destructor of the type of the expression: Otherwise calling the promoted version wouldn't call the destructor at the end of the statement, when the temporary version would.
该表达式没有破坏者,基本上没有针对表达式类型的破坏者调用的下降实现:否则调用促进版本不会在临时版本的末尾在语句结束时调用distuructor。
implementation invoked for the destructor of the type of the expression: Otherwise calling the promoted version wouldn't call the destructor at the end of the statement, when the temporary version would. No interior mutability: If this were to be allowed, each time the expression is called, it could mutate the static memory location, giving a different result each time!
为表达式类型的破坏者调用的实现:否则调用促进版本不会在临时版本的末尾调用destructor。没有内部可突变性:如果允许这样做,每次调用表达式时,它都会突变静态存储位置,每次都会给出不同的结果!
And this explains why this compiles:
这解释了为什么这会汇编:
struct Foo; fn main () { let x = Some( & Foo); x; }
结构foo;fn main(){令x = some(&foo);x;}
Foo doesn't implement Drop , has no interior mutability, and the Foo expression can be evaluated at compile-time. Therefore, Foo is promoted to an 'static , and &Foo returns a reference to a static value that lives until the end of the program.
Foo不会实现下降,没有内部可突变性,并且可以在编译时评估Foo表达。因此,FOO被提升为静态,并且&&&&&&foo返回对静态价值的引用,直到程序结束为止。
While this program doesn't.
虽然这个程序没有。
struct Foo; impl Drop for Foo { fn drop ( & mut self ) {} } fn main () { let x = Some( & Foo); x; }
结构foo;for for foo {fn drop(&mut self){}} fn main(){让x = some(&foo);x;}
Since we implement Drop for type Foo , there's no const promotion for the expression Foo . And so, &Foo is a reference to a memory location that only lives until the end of the let statement.
由于我们实现了FOO类型的下降,因此没有const促进表达式foo。因此,&foo是对只有直到Let语句结束的内存位置的引用。
Note that this also has further implications for const promotions; the value of the returned expression can be used anywhere where an &'static reference is expected, making the following program valid.
请注意,这也对const促进也有进一步的影响。返回表达式的值可以在预期AN&'静态引用的任何地方使用,从而使以下程序有效。
struct Foo; fn main () { let x = & Foo; foo (x); } fn foo ( x : & 'static Foo) {}
结构foo;fn main(){让x =&foo;foo(x);} fn foo(x:&'static foo){}
While this program isn't
虽然这个程序不是
struct Foo; impl Drop for Foo { fn drop ( & mut self ) {} } fn main () { let x = & Foo; foo (x); } fn foo ( x : & 'static Foo) {}
结构foo;for for foo {fn drop(&mut self){}} fn main(){让x =&foo;foo(x);} fn foo(x:&'static foo){}
So, with this you understand lifetimes of temporaries—wait... this compiles though!
因此,这样一来,您就会了解临时性的一生 - 等待...虽然这是汇编的!
struct Foo; impl Drop for Foo { fn drop ( & mut self ) {} } fn main () { let x = & Foo; x; }
结构foo;for for foo {fn drop(&mut self){}} fn main(){让x =&foo;x;}
Well... it's finally time we get into lifetime extension.
好吧...终于是时候进入终身延期了。
First, let's check another example:
首先,让我们检查另一个示例:
struct Foo; impl Drop for Foo { fn drop ( & mut self ) {} } fn main () { let x; x = & Foo; x; }
结构foo;for for fOO {fn drop(&mut self){}} fn main(){让x;x =&foo;x;}
Aaand—this doesn't compile (next time anyone tells you that let x =
AAAND - 这不编译(下次有人告诉您,让X =
This is the error for the program.
这是程序的错误。
error[E0716]: temporary value dropped while borrowed --> src/main.rs:8:10 | 8 | x = &Foo; | ^^^- temporary value is freed at the end of this statement | | | creates a temporary value which is freed while still in use 9 | x; | - borrow later used here | help: consider using a `let` binding to create a longer lived value | 8 ~ let binding = Foo; 9 ~ x = &binding; |
错误[E0716]:借用 - > src/main.rs:8:10 |时,临时值下降了8 |x =&foo;|^^^ - 临时值在此语句的结尾释放|||创建一个暂时的价值,该价值仍在使用时释放9 |x;|- 后来在这里使用|帮助:考虑使用“让'绑定”来创建更长的寿命|8〜LET binding = foo;9〜x =&binding;|
Exactly what we would expect with the previous Some example. Then what?! Is there something special about initializers in let statements? Well... YES!
正是我们在上一个示例中所期望的。那呢?在Let语句中,初始化器有什么特别之处吗?好...是的!
If you paid attention to the definition of temporaries, you might have noticed that it says that:"The drop scope of the temporary is usually the end of the enclosing statement," emphasis on the usually. Or more explicitly in the definition of the drop scope of a temporary:"Apart from lifetime extension, the temporary scope of an expression is the smallest scope that contains the expression [...]".
如果您关注临时性的定义,您可能已经注意到它说:“临时性的下降范围通常是封闭陈述的终结,”通常强调。或更明确地在暂时的下降范围的定义中:“除了寿命扩展外,表达式的临时范围是包含表达式[...]的最小范围”。
So what's going on? Lifetime extensions are an exception for the drop scope of temporaries:
那是怎么回事?终生扩展是临时范围的一个例外:
The temporary scopes for expressions in let statements are sometimes extended to the scope of the block containing the let statement. This is done when the usual temporary scope would be too small, based on certain syntactic rules.
LET语句中表达式的临时范围有时扩展到包含LET语句的块的范围。当通常的临时范围太小,基于某些句法规则时,这是这样做的。
The first important thing to note is that lifetime extensions extend the lifetime of temporary memory locations to the whole scope of the block containing the let statement. Which most of the time will make the actual aliased value in the let expression have a matching lifetime to any temporary reference it might hold. At least, this is the intended purpose, but the definition doesn't depend on an alias existing, so something like let _ = ... could still cause a lifetime extension.
要注意的第一个重要的事情是,寿命扩展将临时存储位置的寿命扩展到包含LET语句的整个范围。在大多数情况下,LET表达式中的实际别名值与其可能拥有的任何临时参考具有匹配的寿命。至少,这是预期的目的,但是定义不取决于存在的别名,因此让_ = ...类似的东西仍然会导致终身扩展。
Note that lifetime extensions only happen to temporaries associated with let statements, that's why it doesn't happen in this example, since the temporary is created for an assignment operation expression and not a let statement.
请注意,生命周期扩展只发生在与Let语句相关的临时性发生,这就是为什么在此示例中不会发生这种情况,因为为分配操作表达式而不是让Let语句创建了临时性。
struct Foo; impl Drop for Foo { fn drop ( & mut self ) {} } fn main () { let x; x = & Foo; x; }
结构foo;for for fOO {fn drop(&mut self){}} fn main(){让x;x =&foo;x;}
We haven't yet defined the specific conditions on when the lifetime extensions happen, but keep in mind that it says that:
我们尚未定义终生延长何时发生的具体条件,但请记住,它说:
If a borrow, dereference, field, or tuple indexing expression has an extended temporary scope then so does its operand. If an indexing expression has an extended temporary scope then the indexed expression also has an extended temporary scope.
如果借用,解雇,字段或元组索引表达式具有扩展的临时范围,则其操作数也是如此。如果索引表达式具有扩展的临时范围,则索引表达式也具有扩展的临时范围。
We will come back to this later. There are 2 cases for lifetime extension: extensions based on patterns and extensions based on expressions.
我们将稍后再回到这一点。生命周期扩展的情况有2种情况:基于模式和基于表达式的扩展的扩展。
Until now all the examples have been cases of the latter, so let's explore that first.
到目前为止,所有示例都是后者的案例,所以让我们先探讨这一点。
The reference states:
参考状态:
For a let statement with an initializer, an extending expression is an expression which is one of the following: The initializer expression.
对于带有初始器的LET语句,扩展的表达式是以下表达式:初始化器表达式之一。
The operand of an extending borrow expression.
扩展借贷表达的操作数。
The operand(s) of an extending array, cast, braced struct, or tuple expression.
扩展阵列,铸造,支撑结构或元组表达式的操作数。
The final expression of any extending block expression.
任何扩展块表达式的最终表达式。
This tells us when an expression is considered"extending," but it doesn't tell us what that really means. For that, we need to look at this paragraph:
这告诉我们何时将表达式视为“扩展”,但这并不告诉我们这是什么意思。为此,我们需要查看本段:
The operand of any extending borrow expression has its temporary scope extended.
任何扩展的借贷表达式的操作数都延长了其临时范围。
There's some kind of a"recursive" behavior with this definition. The initializer expression itself (right-hand side of a let statement) is an extending expression; that expression can contain an extending expression if it meets any of the requisites. For example, if it's a borrow or a tuple, its operands are also extending themselves and so the same goes on for any of those operands.
这个定义有某种“递归”行为。初始化器表达式本身(LET语句的右侧)是一个扩展的表达式。如果符合任何必需品,则该表达式可以包含扩展的表达式。例如,如果是借用或元组,其操作数也在扩展自己,因此对于任何操作数中的任何操作数也是如此。
For example, in let x = Foo the expression Foo is an extending expression, though no lifetime is extended because there's no borrow. In let x = &Foo , the expression &Foo is an extending expression, and Foo , being the operand of an extending borrow expression, has its lifetime extended, and it's also an extending expression.
例如,在让x = foo中,表达式foo是一个扩展的表达式,尽管由于没有借用而没有延长寿命。在让X =&foo中,表达式和foo是一个扩展的表达式,而foo是扩展借贷表达式的操作数,其寿命扩展,并且也是扩展的表达式。
And for a more complicated case: let x = &((), ((), &Foo)); , the expression &((), ((), &Foo)) itself is an initializer expression, so the operand ((), ((), &Foo)) has its lifetime extended. This also means that ((), ((), &Foo)) is an extending expression. Which, in turn, means that each of the operands of the outer tuple are also extending expressions, in particular, ((), &Foo) is an extending expression. Therefore, its operands are also extending, so &Foo is an extending expression. Then, since Foo is the operand of the extending expression, its temporary has its lifetime extended.
对于更复杂的情况:令x =&((),((),&foo));,expression&((),((),&foo)本身是一个初始化器表达式,因此OperAnd((),((),((),((),((),((()))的寿命扩展。这也意味着((),((),&foo))是一个扩展的表达式。反过来,这意味着外元组的每个操作数也在扩展表达式,特别是((),&foo)是一个扩展的表达式。因此,其操作数也在扩展,因此&foo是一个扩展的表达式。然后,由于Foo是扩展表达式的操作数,因此其临时寿命扩展。
Note also that Some(
还请注意,某些(
So finally, FINALLY! We can understand our previous examples.
所以最后,终于!我们可以理解我们以前的示例。
struct Foo; impl Drop for Foo { fn drop ( & mut self ) {} } fn main () { let x = & Foo; x; }
结构foo;for for foo {fn drop(&mut self){}} fn main(){让x =&foo;x;}
The &Foo in let x = &Foo is an extending expression, as the initializer expression of a let statement. Since Foo is the operand of the borrow of an extending expression, the temporary allocated for it has its lifetime extended to the whole main block. Then, for the next line, x; , x is still alive!
LET X =&FOO中的&foo是一个扩展的表达式,作为LET语句的初始化器表达式。由于Foo是借用扩展表达式的操作数,因此分配的临时寿命扩展到整个主块。然后,对于下一行x;,X还活着!
In contrast, in this example:
相比之下,此示例:
struct Foo; impl Drop for Foo { fn drop ( & mut self ) {} } fn main () { let x = Some( & Foo); x; }
结构foo;for for foo {fn drop(&mut self){}} fn main(){让x = some(&foo);x;}
While Some(&Foo) , as the initializer of a let statement, is an extending expression, its operands are not. Therefore, &Foo isn't extending, and no lifetime extension takes place. Meaning that the temporary for Foo goes out of scope at the end of the let statement, and for the next line, if x was still alive, it'd point to uninitialized memory.
虽然某些(&foo)作为LET语句的初始化器是一个扩展的表达式,但其操作数不是。因此,&foo不会延长,也不会发生终生扩展。这意味着FOO的临时性在LET语句的末尾不在范围内,对于下一行,如果X还活着,则指向非初始化的内存。
And we've come so far to actually leave it here, so accompany me to take a quick look at extensions based on patterns. I promise I'll keep it a bit simpler.
而且,我们已经走得太远了,实际上将其留在这里,所以陪伴我根据模式快速查看扩展。我保证我会更简单。
So you might be wondering what a pattern is. For expediency's sake, patterns are the places where values are matched and bound, like the branches of a match , or, most importantly for our case, the left-hand side of a let statement. For example, the x in let x = Foo; is a pattern, and so is (_, x) in let (_, x) = (5, Some(Foo)); .
因此,您可能想知道什么是模式。为了使权宜之计,模式是值匹配和绑定的地方,例如匹配项的分支,或者最重要的是,对于我们的情况,是Let语句的左侧。例如,LET x = foo中的x;是一种模式,在let(_,x)=(5,love(foo))中,也就是(_,x);。
The reference tells us that an extending pattern is either:
引用告诉我们,扩展模式是:
An identifier pattern that binds by reference or mutable reference.
通过参考或可突出参考结合的标识符模式。
A struct, tuple, tuple struct, or slice pattern where at least one of the direct subpatterns is an extending pattern.
一个结构,元组,元组结构或切片模式,其中至少一个直接子图案是一个扩展的模式。
Meaning, the x in let x = Foo; isn't an extending pattern, while ref x in let ref x = Foo; is.
意思是,x中的x = foo;不是扩展模式,而Ref X中的Ref X = Foo;是。
The reference then tells us exactly when lifetime extension happens based on an extending pattern.
然后,参考可以确切地告诉我们何时根据扩展模式发生寿命扩展。
If the pattern in a let statement is an extending pattern then the temporary scope of the initializer expression is extended.
如果LET语句中的模式是扩展的模式,则将扩展初始化器表达式的临时范围。
Okay, simple enough.
好的,很简单。
Let's look at this program:
让我们看一下这个程序:
struct Foo(Bar); #[ derive (Clone, Copy)] struct Bar; impl Drop for Foo { fn drop ( & mut self ) { println!("foo dropped" ); } } fn main () { let x = Foo(Bar). 0 ; println!("last line of the program" ); }
结构foo(bar);#[derive(clone,copy)] struct bar;for for for foo {fn drop(&mut self){println!(“ foo dropped”);}} fn main(){让x = foo(bar)。0;println!(“程序的最后一行”);}
It prints:
它打印:
foo dropped last line of the program
foo删除了程序的最后一行
Field access operands are place expression contexts, Foo(Bar) in Foo(Bar).0 is that kind of operand. Since it's a value expression, it's placed in a temporary. Foo(Bar).0 copies Bar itself into x , but Foo remains at the temporary memory location and is then dropped at the end of the let statement. Note that there's no lifetime extension due to an extending expression since there's no borrow. And no pattern-based extension, since x isn't an extending pattern.
现场访问操作数是位置表达式上下文,foo(bar)中的foo(bar).0就是这种操作数。由于它是一个价值表达式,因此将其放置在一个临时。foo(bar).0将bar本身复制到x中,但是foo保留在临时内存位置,然后将其放在Let语句的末尾。请注意,由于没有借贷,因此没有终生延长。而且没有基于模式的扩展名,因为X不是扩展模式。
However, in this example there's pattern-based extension.
但是,在此示例中,有基于模式的扩展。
struct Foo(Bar); #[ derive (Clone, Copy)] struct Bar; impl Drop for Foo { fn drop ( & mut self ) { println!("foo dropped" ); } } fn main () { let ref x = Foo(Bar). 0 ; println!("last line of the program" ); }
结构foo(bar);#[derive(clone,copy)] struct bar;for for for foo {fn drop(&mut self){println!(“ foo dropped”);}} fn main(){让Ref x = foo(bar)。0;println!(“程序的最后一行”);}
This prints:
这打印:
last line of the program foo dropped
程序的最后一行掉落
Since ref x is an extending pattern, the expression Foo(Bar).0 has its lifetime extended, and from a few paragraphs above, you might remember that"If a borrow, dereference, field, or tuple indexing expression has an extended temporary scope, then so does its operand." This means that the temporary that holds Foo has its lifetime extended, causing it to drop at the end of the main block, after the last line of the program.
由于ref x是一种扩展的模式,因此表达式foo(bar).0具有其寿命延长,并且从上面的几段中,您可能会记住:“如果借用,借用,放电,场或元组索引索引表达式具有扩展的临时范围,那么其操作数也是如此。”这意味着持有FOO的临时性具有其寿命扩展,导致该程序的最后一行之后,它在主块的末端下降。
Note that, if no lifetime extension happened, this program would not compile.
请注意,如果没有终生扩展,则该程序将不会编译。
struct Foo(Bar); #[ derive (Clone, Copy)] struct Bar; impl Drop for Foo { fn drop ( & mut self ) { println!("foo dropped" ); } } fn main () { let ref x = Foo(Bar). 0 ; x; println!("last line of the program" ); }
结构foo(bar);#[derive(clone,copy)] struct bar;for for for foo {fn drop(&mut self){println!(“ foo dropped”);}} fn main(){让Ref x = foo(bar)。0;x;println!(“程序的最后一行”);}
Since the line let ref x = Foo(Bar).0; doesn't copy Bar , but rather keeps a reference to the temporary memory location. Which, without lifetime extension, would be dropped at the end of that same line.
由于该行让Ref x = foo(bar).0;不复制栏,而是保留对临时内存位置的参考。如果没有终生扩展,则将在同一行的末端删除。
Rejoice! You've gone through great trials and tribulations, and now you understand the magic of Rust temporaries.
麾!您已经经历了巨大的考验和磨难,现在您了解了生锈暂时的魔力。
Normally, you don't need to understand this. When the compiler complains, you can just create a new binding and move on and ignore all this otherwise. But I, for one, believe that it's good to understand the specific conditions for the errors given by the compiler.
通常,您不需要理解这一点。当编译器抱怨时,您可以创建一个新的绑定并继续前进并忽略所有这些。但是我相信,了解编译器给出的错误的特定条件是很好的。
I still think it's a bit frustrating having a mismatch between the terminology of the errors given by the compiler and the terms used in the reference. But I also understand having a clear error while using"temporary memory" might be impossible.
我仍然认为,在编译器给出的错误术语与参考中使用的术语之间的术语之间有不匹配,这有点令人沮丧。但是我也知道在使用“临时内存”时遇到明显的错误。
Furthermore, the reference itself sometimes refers to something called"temporary values." Although it links to the section about temporary memory. I guess it'd be trivial to define"temporary value" as the value contained in"temporary memory," but I'm not too sure if that's a good definition, as the value itself would be only temporarily temporary.
此外,参考本身有时是指称为“临时值”的东西。尽管它链接到有关临时内存的部分。我猜想将“临时值”定义为“临时内存”中所包含的值是微不足道的,但是我不太确定这是否是一个很好的定义,因为值本身只是暂时的。