問(wèn)題:
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書(shū)中的生命周期的章節(jié)的時(shí)候,我遇到了這個(gè)例子,這個(gè)例子是為了說(shuō)明命名的作箍、顯示的生命周期:
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.
對(duì)我來(lái)說(shuō)是非常清晰的,這個(gè)是編譯器防止賦值給x的引用”釋放后使用“的問(wèn)題:在內(nèi)部作用域結(jié)束之后前硫,f 以及進(jìn)而&f.x變?yōu)闊o(wú)效胞得,所以不應(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;)
我的問(wèn)題是屹电,在沒(méi)有使用顯示的'a生命周期阶剑,問(wèn)題可以容易的被分析出來(lái),比如,對(duì)于一個(gè)更廣作用域(x = &f.x)危号,可以推斷出一個(gè)引用的賦值是錯(cuò)誤非法的牧愁。
In which cases are explicit lifetimes actually needed to prevent use-after-free (or some other class?) errors?
那在哪種情況下,確實(shí)需要顯示的標(biāo)注生命周期來(lái)防止”釋放后使用“(或者一些其它類(lèi)型)的錯(cuò)誤外莲?
評(píng)論:
For future readers of this question, please note it links to the first edition of the book and there's now a?second edition?:)
對(duì)于這個(gè)問(wèn)題的未來(lái)閱讀者猪半,請(qǐng)注意它鏈接的是書(shū)的第一個(gè)版本兔朦,現(xiàn)在有第二個(gè)版本了:)
回答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舉出了需要一個(gè)顯示的生命周期標(biāo)記的具體例子),但是缺少一個(gè)關(guān)鍵的事情:當(dāng)編譯器告訴你你已經(jīng)弄錯(cuò)了他們的時(shí)候磨确,為什么還需要顯示的生命周期呢?
This is actually the same question as "why are explicit types needed when the compiler can infer them". A hypothetical example:
這個(gè)實(shí)際上和 "why are explicit types needed when the compiler can infer them" 是相同的問(wèn)題沽甥。一個(gè)假設(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)然,編譯器能夠明白我正在返回一個(gè)&'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)編譯器能夠看到你的代碼在做什么的時(shí)候,它不知道你的意圖是什么邓了。
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ù)是防范改變代碼帶來(lái)影響的自然邊界恨诱。如果我們?cè)试S從代碼中完全的檢查生命周期,那么一個(gè)看起來(lái)無(wú)害的改變可能會(huì)影響生命周期驶悟,導(dǎo)致遠(yuǎn)處的函數(shù)中出現(xiàn)錯(cuò)誤胡野。這不是一個(gè)假設(shè)的例子。根據(jù)我的了解痕鳍,當(dāng)你依賴(lài)于頂級(jí)函數(shù)的類(lèi)型推斷時(shí)硫豆,Haskell就有這個(gè)問(wèn)題。Rust將這個(gè)特殊的問(wèn)題扼殺在萌芽中笼呆。
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:
對(duì)于編譯器還有一個(gè)效率上的優(yōu)點(diǎn)--僅函數(shù)簽名(譯注:這里應(yīng)該指函數(shù)定義)需要被解析熊响,目的是為了驗(yàn)證類(lèi)型和生命周期。更重要的是诗赌,對(duì)于程序員能夠提升效率汗茄。如果我們沒(méi)有顯示的標(biāo)記生命周期,這個(gè)函數(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.
不檢查源代碼就無(wú)法知曉铭若,這違反了大量的編碼最佳實(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.
”對(duì)于一個(gè)更廣作用域,可以推斷出一個(gè)引用的賦值是錯(cuò)誤非法的“
本質(zhì)上作用域就是生命周期叼屠。更清楚一點(diǎn)瞳腌,一個(gè)生命周期'a 是一個(gè)通用的生命周期參數(shù),它在編譯時(shí)可以專(zhuān)門(mén)用于一個(gè)指定作用域镜雨,基于調(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)注生命周期來(lái)防止[...]錯(cuò)誤“
不完全是。確實(shí)需要生命周期來(lái)防止一些錯(cuò)誤荚坞,而需要顯示的生命周期是為了保護(hù)頭腦不太明智的程序員挑宠。
評(píng)論:
@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 想象你有一些頂級(jí)函數(shù)f x = x + 1 ,它們沒(méi)有一個(gè)類(lèi)型的簽名(定義)颓影,你正在另一個(gè)模塊使用各淀。如果你稍后改變定義為f x = sqrt $ x + 1,它的類(lèi)型從Num a => a -> a 改變?yōu)?Floating a => a -> a,那么當(dāng)f函數(shù)帶Int(比如)參數(shù)被調(diào)用時(shí),所有調(diào)用的位置都會(huì)引起類(lèi)型錯(cuò)誤诡挂。有一個(gè)類(lèi)型簽名(定義)確定的話(huà)揪阿,錯(cuò)誤只會(huì)發(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)南捂,一個(gè)生命周期'a 是一個(gè)通用的生命周期參數(shù)吴裤,它在編譯時(shí)可以專(zhuān)門(mén)用于一個(gè)指定作用域“。哇哦溺健,它真的很棒麦牺,給人啟發(fā)。如果它顯示的包含在書(shū)中我將非常高興鞭缭。
@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 $前類(lèi)型被顯示的聲明岭辣,那么在改變之后僅僅會(huì)有一個(gè)局部錯(cuò)誤吱晒,而不是在其它地方有很多錯(cuò)誤(如果我們不會(huì)改變目前的類(lèi)型則好很多)?
@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 的確如此沦童。沒(méi)有指定一個(gè)類(lèi)型意味著一個(gè)函數(shù)的接口你能夠意外的改變仑濒。這個(gè)就是為什么在Haskell語(yǔ)言中要強(qiáng)烈鼓勵(lì)注釋所有頂級(jí)項(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.
同樣偷遗,如果一個(gè)函數(shù)接收兩個(gè)引用墩瞳,并且翻譯一個(gè)引用,那么它可能有時(shí)候返回第一個(gè)引用氏豌,有時(shí)候返回第二個(gè)引用喉酌。在這種情況下推斷返回值引用的生命周期是不可能的。
回答2:
Let's have a look at the following example.
讓我們看一下下面這個(gè)例子泵喘。
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:
這里泪电,顯示的生命周期是重要的。這個(gè)是可以編譯的纪铺,因?yàn)閒oo的結(jié)果與第一個(gè)參數(shù)('a)的生命周期相同相速,所以它可能比第二個(gè)參數(shù)活的更長(zhǎng)久。這是通過(guò)在foo的簽名(定義)中生命周期的命名來(lái)表達(dá)的霹陡。如果在調(diào)用foo時(shí)你切換了參數(shù)和蚪,編譯器抱怨說(shuō)y不能活的足夠長(zhǎng)止状。
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