为什么生命周期名称显示为函数类型的一部分?

Way*_*rad 19 lifetime rust

我相信这个函数声明告诉Rust函数输出的生命周期与它的s参数的生命周期相同:

fn substr<'a>(s: &'a str, until: u32) -> &'a str;
         ^^^^
Run Code Online (Sandbox Code Playgroud)

在我看来,编译器只需要知道这个(1):

fn substr(s: &'a str, until: u32) -> &'a str;
Run Code Online (Sandbox Code Playgroud)

<'a>函数名后面的注释是什么意思?为什么编译器需要它,它用它做什么?


(1):我知道由于终生的缺席,它需要知道的更少.但这个问题是关于明确指定生命周期.

sel*_*tze 30

让我扩展以前的答案......

函数名后的注释<'a>是什么意思?

我不会为此使用"注释"这个词.与<T>引入泛型类型参数非常相似,<'a>引入了通用生命周期参数.如果不首先引入它们,就不能使用任何通用参数,对于通用函数,这个引入恰好在它们的名称之后发生.您可以将通用函数视为一系列函数.因此,基本上,您可以为每个通用参数组合获得一个函数.substr::<'x>在某些生命中,它将成为该函数族的特定成员'x.

如果你不清楚何时以及为什么我们必须明确生命,请继续阅读......

生命周期参数始终与所有引用类型相关联.当你写作

fn main() {
    let x = 28374;
    let r = &x;
}
Run Code Online (Sandbox Code Playgroud)

编译器知道x存在于用大括号括起来的main函数的作用域中.在内部,它使用一些生命周期参数来标识此范围.对我们来说,它没有命名.获取地址后x,您将获得特定参考类型的值.引用类型是二维引用类型族的一种.一个轴是参考指向的类型,另一个轴是用于两个约束的生命周期:

  1. 引用类型的lifetime参数表示您可以保留该引用的时间长度的上限
  2. 引用类型的lifetime参数表示可以使引用指向的内容的生命周期的下限.

这些约束一起在Rust的记忆安全故事中起着至关重要的作用.这里的目标是避免悬挂引用.我们想排除指向一些我们不允许再使用的内存区域的引用,因为它曾经指向的东西不再存在.

一个潜在的混淆源可能是寿命参数在大多数时间是不可见的.但这并不意味着他们不存在.引用在其类型中始终具有生命周期参数.但是这样的生命周期参数不必具有名称,并且大多数时候我们不需要提及它,因为编译器可以自动为生命周期参数分配名称.这被称为"终身省略".例如,在以下情况中,您没有看到提到的任何生命周期参数:

fn substr(s: &str, until: u32) -> &str {…}
Run Code Online (Sandbox Code Playgroud)

但是可以像这样写它.它实际上是更明确的快捷语法

fn substr<'a>(s: &'a str, until: u32) -> &'a str {…}
Run Code Online (Sandbox Code Playgroud)

在这里,编译器自动为"输入生命周期"和"输出生命周期"分配相同的名称,因为它是一种非常常见的模式,很可能正是您想要的.因为这种模式是如此常见,编译器让我们逃避而不说任何关于生命期的事情.它假设这个更明确的形式是我们基于几个"终身省略"规则的意思(至少在这里记录)

在某些情况下,显式生命周期参数不是可选的.例如,如果你写

fn min<T: Ord>(x: &T, y: &T) -> &T {
    if x <= y {
        x
    } else {
        y
    }
}
Run Code Online (Sandbox Code Playgroud)

编译器会抱怨,因为它会将上述声明解释为

fn min<'a, 'b, 'c, T: Ord>(x: &'a T, y: &'b T) -> &'c T { … }
Run Code Online (Sandbox Code Playgroud)

因此,对于每个引用,引入单独的寿命参数.但是,此签名中没有关于生命周期参数如何相互关联的信息.此通用函数的用户可以使用任何生命周期.而这是身体内部的一个问题.我们试图返回x或者y.但是类型x&'a T.这与返回类型不兼容&'c T.同样如此y.由于编译器对这些生命周期如何相互关联一无所知,因此将这些引用作为类型的引用返回是不安全的&'c T.

它可以永远是安全的,从类型的值去&'a T&'c T?是.如果寿命'a等于或大于寿命,则是安全的'c.或者换句话说'a: 'c.所以,我们可以写这个

fn min<'a, 'b, 'c, T: Ord>(x: &'a T, y: &'b T) -> &'c T
      where 'a: 'c, 'b: 'c
{ … }
Run Code Online (Sandbox Code Playgroud)

并且在没有编译器抱怨函数的主体的情况下逃脱它.但它实际上是不必要的复杂.我们也可以简单地写

fn min<'a, T: Ord>(x: &'a T, y: &'a T) -> &'a T { … }
Run Code Online (Sandbox Code Playgroud)

并为所有内容使用单个生命周期参数.编译器能够'a在调用站点推断出参数引用的最小生命周期,因为我们对两个参数使用了相同的生命周期名称.而这一生命正是我们对返回类型所需要的.

我希望这回答了你的问题.:)干杯!

  • 对于生命的意义而言,这是一个很好的答案,而不仅仅是我的答案!它也方便地解释了为什么我的"废话示例"实际上是无稽之谈! (2认同)

She*_*ter 16

函数名后的注释<'a>是什么意思?

fn substr<'a>(s: &'a str, until: u32) -> &'a str;
         ^^^^
Run Code Online (Sandbox Code Playgroud)

这是一个通用的生命周期参数.它类似于泛型类型参数(通常被视为<T>),因为函数的调用者可以决定生命周期是什么.就像你说的那样,结果的生命周期与第一个参数的生命周期相同.

所有生命周期的名称都是等效的,除了一个:'static.这个生命周期预先设定为"保证在整个计划期间生活".

最常见的生命周期参数名称可能是'a,但您可以使用任何字母或字符串.单个字母是最常见的,但任何snake_case标识符都是可接受的.

为什么编译器需要它,它用它做什么?

除非有非常好的人体工程学优势,否则Rust通常倾向于明确要求.对于有生之年,终身消费可以照顾85%以上的案件,这似乎是一个明显的胜利.

类型参数与其他类型存在于同一名称空间中 - 是T泛型类型还是有人将结构命名为?因此,类型参数需要具有显式注释,该注释显示该T参数而不是实际类型.但是,生命周期参数没有同样的问题,所以这不是原因.

相反,显式列出类型参数的主要好处是因为您可以控制多个参数的交互方式.一个无意义的例子:

fn better_str<'a, 'b, 'c>(a: &'a str, b: &'b str) -> &'c str
    where 'a: 'c,
          'b: 'c,
{
    if a.len() < b.len() { a } else { b }
}
Run Code Online (Sandbox Code Playgroud)

我们有两个字符串,并说输入字符串可能具有不同的生命周期,但必须比结果值的生命周期更长.

另一个例子,正如DK指出的那样,结构可以有自己的生命周期.我让这个例子也有点废话,但它希望传达这一点:

struct Player<'a> {
    name: &'a str,
}

fn name<'p, 'n>(player: &'p Player<'n>) -> &'n str {
    player.name
}
Run Code Online (Sandbox Code Playgroud)

生命周期可能是Rust中令人费解的部分之一,但是当你开始掌握它们时它们非常棒.

  • 还要考虑当你在方法`impl`中使用的结构上有一个生命周期参数时会发生什么.如何编译器知道生命周期应该绑定到谁? (2认同)