为什么生命周期名称作为函数类型的一部分出现?

Why does the lifetime name appear as part of the function type?

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

fn substr<'a>(s: &'a str, until: u32) -> &'a str;
         ^^^^

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

fn substr(s: &'a str, until: u32) -> &'a str;

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


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

What does the annotation <'a> after the function name mean?

fn substr<'a>(s: &'a str, until: u32) -> &'a str;
//       ^^^^

这是在声明一个通用的生命周期参数。它类似于通用 类型参数 (通常被视为 <T>),因为函数的 caller 决定一生是。正如您所说,结果的生命周期将与第一个参数的生命周期相同。

所有生命周期名称都是等价的,除了一个:'static。此生命周期预设为 "guaranteed to live for the entire life of the program"。

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

Why does the compiler need it, and what does it do with it?

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
    }
}

我们有两个字符串,输入字符串可能有不同的生命周期,但都必须超过结果值的生命周期。

另一个例子,如,结构可以有自己的生命周期。我把这个例子也写得有点废话,但它希望能表达这一点:

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

fn name<'p, 'n>(player: &'p Player<'n>) -> &'n str {
    player.name
}

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

<'a> 注解只是声明函数中使用的生命周期,与泛型参数完全一样 <T>

fn subslice<'a, T>(s: &'a [T], until: u32) -> &'a [T] { \'
    &s[..until as usize]
}

请注意,在您的示例中,可以推断出所有生命周期。

fn subslice<T>(s: &[T], until: u32) -> &[T] {
    &s[..until as usize]
}

fn substr(s: &str, until: u32) -> &str {
    &s[..until as usize]
}

playpen example

让我扩展一下之前的答案……

What does the annotation <'a> after the function name mean?

我不会为此使用 "annotation" 这个词。很像 <T> 引入了一个通用的 type 参数,<'a> 引入了一个通用的 lifetime 参数。如果不先引入泛型参数,就不能使用任何泛型参数,对于泛型函数,这种引入紧跟在它们的名称之后。您可以将泛型函数视为一个函数族。因此,从本质上讲,您会为通用参数的每种组合获得一个函数。 substr::<'x> 将成为该函数族的特定成员一段时间 'x

如果您不清楚我们何时以及为何必须明确说明生命周期,请继续阅读……

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

fn main() {
    let x = 28374;
    let r = &x;
}

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

  1. 引用类型的生命周期参数表示您可以保留该引用多长时间的上限
  2. 引用类型的生命周期参数表示您可以使引用指向的对象的生命周期下限。

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

一个潜在的混淆来源可能是生命周期参数在大多数时候是不可见的。但这并不意味着他们不存在。引用 always 在它们的类型中有一个生命周期参数。但是这样的生命周期参数不一定要有名字,而且大多数时候我们也不需要提及它,因为编译器可以自动为生命周期参数分配名称。这叫做"lifetime elision"。例如,在以下情况中,您不会看到 提到任何生命周期参数:

fn substr(s: &str, until: u32) -> &str {…}

不过这样写也无妨。它实际上是一个 short-cut 语法,用于更明确的

fn substr<'a>(s: &'a str, until: u32) -> &'a str {…}

在这里,编译器会自动为 "input lifetime" 和 "output lifetime" 分配相同的名称,因为这是一种非常常见的模式,很可能正是您想要的。因为这种模式很常见,所以编译器让我们不用说任何关于生命周期的事情。它 假设 这种更明确的形式是我们基于几个 "lifetime elision" 规则(至少有记录 here

的意思

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

fn min<T: Ord>(x: &T, y: &T) -> &T {
    if x <= y {
        x
    } else {
        y
    }
}

编译器会报错,因为它会将上面的声明解释为

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

因此,对于每个引用,都引入了一个单独的生命周期参数。但是此签名中没有关于生命周期参数如何相互关联的信息。此通用函数的用户可以使用 any 生命周期。这是它 body 内部的一个问题。我们正在尝试 return xy。但是x的类型是&'a T。这与 return 类型 &'c T 不兼容。 y也是如此。由于编译器不知道这些生命周期如何相互关联,因此 return 这些引用作为 &'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
{ … }

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

fn min<'a, T: Ord>(x: &'a T, y: &'a T) -> &'a T { … }

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

我希望这能回答您的问题。 :) 干杯!