問題:
Why are explicit lifetimes needed in Rust?
在Rust中為什么需要顯示的生命周期?
I was reading the?lifetimes chapter?of the Rust book, and I came across this example for a named/explicit lifetime:
我正在讀Rust書中的生命周期的章節(jié)的時候,我遇到了這個例子,這個例子是為了說明命名的、顯示的生命周期:
struct Foo<'a> {
? ? x: &'a i32,
}
fn main() {
? ? let x;? ? ? ? ? ? ? ? ? ? // -+ x goes into scope
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //? |
? ? {? ? ? ? ? ? ? ? ? ? ? ? //? |
? ? ? ? let y = &5;? ? ? ? ? // ---+ y goes into scope
? ? ? ? let f = Foo { x: y }; // ---+ f goes into scope
? ? ? ? x = &f.x;? ? ? ? ? ? //? | | error here
? ? }? ? ? ? ? ? ? ? ? ? ? ? // ---+ f and y go out of scope
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //? |
? ? println!("{}", x);? ? ? ? //? |
}? ? ? ? ? ? ? ? ? ? ? ? ? ? // -+ x goes out of scope
It's quite clear to me that the error being prevented by the compiler is the?use-after-free?of the reference assigned to?x: after the inner scope is done,?f?and therefore?&f.x?become invalid, and should not have been assigned to?x.
對我來說是非常清晰的,這個是編譯器防止賦值給x的引用”釋放后使用“的問題:在內(nèi)部作用域結(jié)束之后,f 以及進(jìn)而&f.x變?yōu)闊o效,所以不應(yīng)該賦值給x。
My issue is that the problem could have easily been analyzed away?without?using the?explicit?'a?lifetime, for instance by inferring an illegal assignment of a reference to a wider scope (x = &f.x;)
我的問題是,在沒有使用顯示的'a生命周期,問題可以容易的被分析出來,比如,對于一個更廣作用域(x = &f.x),可以推斷出一個引用的賦值是錯誤非法的。
In which cases are explicit lifetimes actually needed to prevent use-after-free (or some other class?) errors?
那在哪種情況下,確實(shí)需要顯示的標(biāo)注生命周期來防止”釋放后使用“(或者一些其它類型)的錯誤?
評論:
For future readers of this question, please note it links to the first edition of the book and there's now a?second edition?:)
對于這個問題的未來閱讀者,請注意它鏈接的是書的第一個版本,現(xiàn)在有第二個版本了:)
回答1(selected):
The other answers all have salient points (fjh's concrete example where an explicit lifetime is needed), but are missing one key thing: why are explicit lifetimes needed when?the compiler will tell you you've got them wrong?
其它答案都有可圈可點(diǎn)的地方(fjh舉出了需要一個顯示的生命周期標(biāo)記的具體例子),但是缺少一個關(guān)鍵的事情:當(dāng)編譯器告訴你你已經(jīng)弄錯了他們的時候,為什么還需要顯示的生命周期呢?
This is actually the same question as "why are explicit types needed when the compiler can infer them". A hypothetical example:
這個實(shí)際上和 "why are explicit types needed when the compiler can infer them" 是相同的問題。一個假設(shè)的例子:
fn foo() -> _ {?
? ? ""
}
Of course, the compiler can see that I'm returning a?&'static str, so why does the programmer have to type it?
當(dāng)然,編譯器能夠明白我正在返回一個&'static str,所以為什么程序員還不得不鍵入它呢?
The main reason is that while the compiler can see what your code does, it doesn't know what your intent was.
主要原因是當(dāng)編譯器能夠看到你的代碼在做什么的時候,它不知道你的意圖是什么。
Functions are a natural boundary to firewall the effects of changing code. If we were to allow lifetimes to be completely inspected from the code, then an innocent-looking change might affect the lifetimes, which could then cause errors in a function far away. This isn't a hypothetical example. As I understand it, Haskell has this problem when you rely on type inference for top-level functions. Rust nipped that particular problem in the bud.
函數(shù)是防范改變代碼帶來影響的自然邊界。如果我們允許從代碼中完全的檢查生命周期,那么一個看起來無害的改變可能會影響生命周期,導(dǎo)致遠(yuǎn)處的函數(shù)中出現(xiàn)錯誤。這不是一個假設(shè)的例子。根據(jù)我的了解,當(dāng)你依賴于頂級函數(shù)的類型推斷時,Haskell就有這個問題。Rust將這個特殊的問題扼殺在萌芽中。
There is also an efficiency benefit to the compiler — only function signatures need to be parsed in order to verify types and lifetimes. More importantly, it has an efficiency benefit for the programmer. If we didn't have explicit lifetimes, what does this function do:
對于編譯器還有一個效率上的優(yōu)點(diǎn)--僅函數(shù)簽名(譯注:這里應(yīng)該指函數(shù)定義)需要被解析,目的是為了驗(yàn)證類型和生命周期。更重要的是,對于程序員能夠提升效率。如果我們沒有顯示的標(biāo)記生命周期,這個函數(shù)做些什么:
fn foo(a: &u8, b: &u8) -> &u8
It's impossible to tell without inspecting the source, which would go against a huge number of coding best practices.
不檢查源代碼就無法知曉,這違反了大量的編碼最佳實(shí)踐。
”by inferring an illegal assignment of a reference to a wider scope“
Scopes?are?lifetimes, essentially. A bit more clearly, a lifetime?'a?is a?generic lifetime parameter?that can be specialized with a specific scope at compile time, based on the call site.
”對于一個更廣作用域,可以推斷出一個引用的賦值是錯誤非法的“
本質(zhì)上作用域就是生命周期。更清楚一點(diǎn),一個生命周期'a 是一個通用的生命周期參數(shù),它在編譯時可以專門用于一個指定作用域,基于調(diào)用的位置。
"are explicit lifetimes actually needed to prevent [...] errors?"
Not at all.?Lifetimes?are needed to prevent errors, but explicit lifetimes are needed to protect what little sanity programmers have.
”確實(shí)需要顯示的標(biāo)注生命周期來防止[...]錯誤“
不完全是。確實(shí)需要生命周期來防止一些錯誤,而需要顯示的生命周期是為了保護(hù)頭腦不太明智的程序員。
評論:
@jco Imagine you have some top-level function?f x = x + 1?without a type signature that you're using in another module. If you later change the definition to?f x = sqrt $ x + 1, its type changes from?Num a => a -> a?to?Floating a => a -> a, which will cause type errors at all the call sites where?f?is called with e.g. an?Int?argument. Having a type signature ensures that errors occur locally.
@jco 想象你有一些頂級函數(shù)f x = x + 1 ,它們沒有一個類型的簽名(定義),你正在另一個模塊使用。如果你稍后改變定義為f x = sqrt $ x + 1,它的類型從Num a => a -> a 改變?yōu)?Floating a => a -> a,那么當(dāng)f函數(shù)帶Int(比如)參數(shù)被調(diào)用時,所有調(diào)用的位置都會引起類型錯誤。有一個類型簽名(定義)確定的話,錯誤只會發(fā)生在局部。
"Scopes are lifetimes, essentially. A bit more clearly, a lifetime 'a is a generic lifetime parameter that can be specialized with a specific scope at call time. "?Wow that's a really great, illuminating point. I'd like it if it was included in the book this explicitly.
”本質(zhì)上作用域就是生命周期。更清楚一點(diǎn),一個生命周期'a 是一個通用的生命周期參數(shù),它在編譯時可以專門用于一個指定作用域“。哇哦,它真的很棒,給人啟發(fā)。如果它顯示的包含在書中我將非常高興。
@fjh Thanks. Just to see if I grok it -- the point is that if the type was explicitly stated before adding?sqrt $, only a local error would have occurred after the change, and not a lot of errors in other places (which is much better if we didn't want to change the actual type)?
@fjh 謝謝。看看我是否理解了 -- 要點(diǎn)是如果在添加sqrt $前類型被顯示的聲明,那么在改變之后僅僅會有一個局部錯誤,而不是在其它地方有很多錯誤(如果我們不會改變目前的類型則好很多)?
@jco Exactly. Not specifying a type means that you can accidentally change the interface of a function. That's one of the reasons that it is strongly encouraged to annotate all top-level items in Haskell.
@jco 的確如此。沒有指定一個類型意味著一個函數(shù)的接口你能夠意外的改變。這個就是為什么在Haskell語言中要強(qiáng)烈鼓勵注釋所有頂級項(xiàng)目的原因。
Also if a function receives two references and returns a reference then it might sometimes return the first reference and sometimes the second one. In this case it is impossible to infer a lifetime for the returned reference. Explicit lifetimes help to avoid/clarify such a situation.
同樣,如果一個函數(shù)接收兩個引用,并且翻譯一個引用,那么它可能有時候返回第一個引用,有時候返回第二個引用。在這種情況下推斷返回值引用的生命周期是不可能的。
回答2:
Let's have a look at the following example.
讓我們看一下下面這個例子。
fn foo<'a, 'b>(x: &'a u32, y: &'b u32) -> &'a u32 {
? ? x
}
fn main() {
? ? let x = 12;
? ? let z: &u32 = {
? ? ? ? let y = 42;
? ? ? ? foo(&x, &y)
? ? };
}
Here, the explicit lifetimes are important. This compiles because the result of?foo?has the same lifetime as its first argument ('a), so it may outlive its second argument. This is expressed by the lifetime names in the signature of?foo. If you switched the arguments in the call to?foo?the compiler would complain that?y?does not live long enough:
這里,顯示的生命周期是重要的。這個是可以編譯的,因?yàn)閒oo的結(jié)果與第一個參數(shù)('a)的生命周期相同,所以它可能比第二個參數(shù)活的更長久。這是通過在foo的簽名(定義)中生命周期的命名來表達(dá)的。如果在調(diào)用foo時你切換了參數(shù),編譯器抱怨說y不能活的足夠長。
error[E0597]: `y` does not live long enough
? --> src/main.rs:10:5
? |
9? |? ? ? ? foo(&y, &x)
? |? ? ? ? ? ? ? - borrow occurs here
10 |? ? };
? |? ? ^ `y` dropped here while still borrowed
11 | }
? | - borrowed value needs to live until here