Rust中的scoped lifetime实际上是什么意思?

6

所以,在:

fn v1<'a> (a:~[&'a str]) -> ~[&'a str] {
  return a;
}

#[test]
fn test_can_create_struct() {
  let x = v1("Hello World".split(' ').collect());
}

我知道,我已经阅读了http://static.rust-lang.org/doc/master/guide-lifetimes.html#named-lifetimes,但我不明白这段代码实际上是做什么的
这个函数基本上像一个带有生命周期的通用fn一样进行参数化,这是我在IRC频道上看到的说法,但让我们想象一下这种情况,并且我们有一个L,它是一些特定的生命周期结构。
显然,我隐式调用了:
v1::<L>("Hello World".split(' ').collect());

但是我并不是这样认为的。传递给该函数的lifetime是一个lifetime实例,而不是lifetime类型,因此那个注释对我来说没有任何意义。

我的意思是,我基本上理解发生了什么(我想):返回的~[&str]与调用者的作用域具有相同的lifetime,可能是test_can_create_struct()函数。这是因为(据我所知),函数v1使用来自调用函数的lifetime实例进行调用。

非常令人困惑。

然后我们有一些其他的例子,比如: https://gist.github.com/bvssvni/8970459

这里是一个片段:

impl<'a> Data<'a> {
  pub fn new() -> Data<'a> {
    Data { a: None, b: None }
  }

  pub fn a(&'a mut self, a: int) -> State<'a, Step1> {
    self.a = Some(a);
    State { data: self }
  }
}

现在我天真地认为 Data<'a> 意味着函数 a() 的生命周期实例是相同的。
也就是说,如果您创建了一个 Datalet blah = Data::new())并调用 blah.a(),则生命周期将从创建调用继承; 也就是说,返回的 State 对象将存在于父 Data 对象存在的时间内。
但显然这也是错误的。所以我现在完全不知道生命周期变量的含义。
帮帮我!

生命周期是类型系统的一部分;它们不是实例。 - Chris Morgan
1个回答

20
所以最简单的回答方式是退后一步,带你了解何为一生。
让我们来看一个简单的函数:
fn simple_function() {
  let a = MyFoo::new();
  println("{}", a);
}

在这个函数中,我们有一个名为 a 的变量。像所有变量一样,该变量存在一定的时间。在此情况下,它存在到函数结束。当函数结束时,a 就会消失。可以描述 a 的生命周期始于函数开始,终于函数结束。

下一个函数将无法编译:

fn broken_function() -> &MyFoo {
  let a = MyFoo::new();
  return &a;
}
当你执行&a时,你正在借用a引用。关于借用的问题在于,你需要将借来的东西归还。Rust对此非常严格,不允许你持有无法返回的引用。如果你从中借用引用的东西不再存在,那么你就无法返回该引用,这是不行的。
对于我们的broken_function,这意味着,因为a在函数结束时消失了,所以该引用无法逃出函数,因为这将使其比a更长寿。
下一步是:
fn call_fn() {
  let a = MyFoo:new();
  {
    let a_ref = &a;
    let b = lifetimed(a_ref);

    println!("{}", *b);
  }
}

fn lifetimed<'a>(foo: &'a MyFoo) -> &'a MyBar {
   return foo.as_bar();
}

这里有两个函数,call_fnlifetimed,其中有一些微妙的东西需要解释一下。

call_fn中,我首先创建了一个新的MyFoo实例并将其分配给a,然后,我借用了对a的引用并将其分配给a_ref。关于借用的事情是,当你进行借用时,生命周期信息从您要借用的变量传递到引用本身。因此,现在作为变量的a_ref具有自己的生命周期,该生命周期从该内部作用域的开始和结束开始,并以a转移过来的方式结束。但是a_ref的类型也具有生命周期,即从a 转移过来的生命周期。

具体的生命周期不能被命名,但让我们假装可以通过使用数字来命名。如果a的生命周期是#1,那么a_ref类型&'#1 MyFoo。当我们将a_ref传递给lifetimed时,编译器会像处理其他类型参数一样填充生命周期参数'alifetimed的返回类型是具有相同生命周期的引用,因此编译器在那里填充了空间。实际上做了一个独特的调用lifetimed(foo: &'#1 MyFoo) -> &'#1 MyBar

这就是为什么生命周期出现在类型参数列表中的原因,它们是类型系统的一部分,如果类型不匹配,那就是错误。编译器会计算出为使函数编译所需的生命周期,因此您永远不必担心它,但它不会查看当前函数以外的信息。您需要使用参数告诉编译器有关您正在调用的函数,以便它知道一切都没问题。


NB:有一种生命周期可以显式命名,即'static,它是整个程序运行时间持续的事物的生命周期。


1
非常感谢您的解释,但是,您能否给出一些实际的例子,说明何时需要为函数声明生命周期?我仍然无法理解这个特性的用例。 - Sergey Kamardin
我认为泛型的使用非常令人困惑。它实际上只是添加了一个标签来创建一个新的作用域/区域,泛型类型意味着为每个'a,T'组合实例化一个新类型,例如1,T,2,T等,而1,T!= 2,T这种情况并不成立。 - user1496062

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接