【翻译】在Rust中为什么需要显示的生命周期?

问题:

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书中的生命周期的章节的时候,我遇到了这个例子,这个例子是为了说明命名的、显示的生命周期:

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的引用”释放后使用“的问题:在内部作用域结束之后,f 以及进而&f.x变为无效,所以不应该赋值给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?

那在哪种情况下,确实需要显示的标注生命周期来防止”释放后使用“(或者一些其它类型)的错误?


评论:

For future readers of this question, please note it links to the first edition of the book and there's now a second edition :)

对于这个问题的未来阅读者,请注意它链接的是书的第一个版本,现在有第二个版本了:)


回答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?

其它答案都有可圈可点的地方(fjh举出了需要一个显示的生命周期标记的具体例子),但是缺少一个关键的事情:当编译器告诉你你已经弄错了他们的时候,为什么还需要显示的生命周期呢?


This is actually the same question as "why are explicit types needed when the compiler can infer them". A hypothetical example:

这个实际上和 "why are explicit types needed when the compiler can infer them" 是相同的问题。一个假设的例子:

fn foo() -> _ { 

    ""

}

Of course, the compiler can see that I'm returning a &'static str, so why does the programmer have to type it?

当然,编译器能够明白我正在返回一个&'static str,所以为什么程序员还不得不键入它呢?


The main reason is that while the compiler can see what your code does, it doesn't know what your intent was.

主要原因是当编译器能够看到你的代码在做什么的时候,它不知道你的意图是什么。


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.

函数是防范改变代码带来影响的自然边界。如果我们允许从代码中完全的检查生命周期,那么一个看起来无害的改变可能会影响生命周期,导致远处的函数中出现错误。这不是一个假设的例子。根据我的了解,当你依赖于顶级函数的类型推断时,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:

对于编译器还有一个效率上的优点--仅函数签名(译注:这里应该指函数定义)需要被解析,目的是为了验证类型和生命周期。更重要的是,对于程序员能够提升效率。如果我们没有显示的标记生命周期,这个函数做些什么:

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.

不检查源代码就无法知晓,这违反了大量的编码最佳实践。


”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.


”对于一个更广作用域,可以推断出一个引用的赋值是错误非法的“

本质上作用域就是生命周期。更清楚一点,一个生命周期'a 是一个通用的生命周期参数,它在编译时可以专门用于一个指定作用域,基于调用的位置。


"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.

”确实需要显示的标注生命周期来防止[...]错误“

不完全是。确实需要生命周期来防止一些错误,而需要显示的生命周期是为了保护头脑不太明智的程序员。


评论:

@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 想象你有一些顶级函数f x = x + 1 ,它们没有一个类型的签名(定义),你正在另一个模块使用。如果你稍后改变定义为f x = sqrt $ x + 1,它的类型从Num a => a -> a 改变为 Floating a => a -> a,那么当f函数带Int(比如)参数被调用时,所有调用的位置都会引起类型错误。有一个类型签名(定义)确定的话,错误只会发生在局部。


"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.

”本质上作用域就是生命周期。更清楚一点,一个生命周期'a 是一个通用的生命周期参数,它在编译时可以专门用于一个指定作用域“。哇哦,它真的很棒,给人启发。如果它显示的包含在书中我将非常高兴。


@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 谢谢。看看我是否理解了 -- 要点是如果在添加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 的确如此。没有指定一个类型意味着一个函数的接口你能够意外的改变。这个就是为什么在Haskell语言中要强烈鼓励注释所有顶级项目的原因。


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.

同样,如果一个函数接收两个引用,并且翻译一个引用,那么它可能有时候返回第一个引用,有时候返回第二个引用。在这种情况下推断返回值引用的生命周期是不可能的。


回答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:

这里,显示的生命周期是重要的。这个是可以编译的,因为foo的结果与第一个参数('a)的生命周期相同,所以它可能比第二个参数活的更长久。这是通过在foo的签名(定义)中生命周期的命名来表达的。如果在调用foo时你切换了参数,编译器抱怨说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

你可能感兴趣的:(【翻译】在Rust中为什么需要显示的生命周期?)