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

问题描述 投票:19回答:3

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

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

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

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

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


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

rust lifetime
3个回答
29
投票

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

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

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

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

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

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

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

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

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

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

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

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

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

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

因此,对于每个引用,引入单独的寿命参数。但是,此签名中没有关于生命周期参数如何相互关联的信息。此通用函数的用户可以使用任何生命周期。而这是身体内部的一个问题。我们试图返回xy。但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
{ … }

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

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

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

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


16
投票

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

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

这是一个通用的生命周期参数。它类似于泛型类型参数(通常被视为<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 }
}

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

另一个例子,就像pointed out by DK一样,结构可以有自己的生命周期。我让这个例子也有点废话,但它有希望传达这一点:

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

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

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


1
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.