让我详细说明一下之前的答案...
引用函数名称后面的注解<'a>是什么意思?我不会用“注解”这个词来描述它。与
<T>
引入泛型类型参数类似,
<'a>
引入了泛型生命周期参数。在不引入泛型参数的情况下无法使用任何泛型参数,对于泛型函数,这种引入发生在函数名称后面。您可以将泛型函数视为函数族。因此,您可以为每个泛型参数组合获得一个函数。例如,
substr::<'x>
就是该函数族的一个特定成员,适用于某个生命周期
'x
。
如果您不清楚何时以及为什么必须明确生命周期参数,请继续阅读...
生命周期参数始终与所有引用类型相关联。当您编写:
fn main() {
let x = 28374;
let r = &x;
}
编译器知道 x 存在于 main 函数的花括号包围的作用域中。在内部,它使用某个生命周期参数来标识此作用域。对我们来说,它是匿名的。当您取地址
x
时,将获得特定引用类型的值。引用类型是引用类型的二维家族的一种成员。一个轴是引用指向的内容的类型,另一个轴是用于两个约束条件的生命周期:
- 引用类型的生命周期参数表示您可以保留该引用的时间上限
- 引用类型的生命周期参数表示您可以使引用指向的事物的生命周期下限。
这些约束条件共同在 Rust 的内存安全故事中发挥着至关重要的作用。这里的目标是避免悬空引用。我们希望排除指向某个我们不允许再使用的内存区域的引用,因为它曾经指向的东西已经不存在了。
可能会引起混淆的一个潜在来源可能是生命周期参数大多数时间是不可见的事实。但这并不意味着它们不存在。引用类型始终具有生命周期参数。但是这样的生命周期参数不必具有名称,并且大多数情况下我们不需要提及它,因为编译器可以自动为生命周期参数分配名称。这称为“生命周期省略”。例如,在以下情况下,您不会看到任何生命周期参数被提及:
fn substr(s: &str, until: u32) -> &str {…}
但是这样写也没问题。实际上,这是更显式的简写语法。
fn substr<'a>(s: &'a str, until: u32) -> &'a str {…}
在这里,编译器自动将“输入生命周期”和“输出生命周期”赋予相同的名称,因为这是一个非常常见的模式,也很可能是您想要的。由于这种模式非常普遍,编译器让我们不必对生命周期说任何话。它假定我们基于一些“生命周期省略”规则(至少在此处有文档
)想要更明确的形式。
有时候,
显式生命周期参数是
不可选的。例如,如果您写下:
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 { … }
因此,对于每个引用,都会引入一个单独的生命周期参数。但是,在此签名中没有关于生命周期参数如何相互关联的信息。这个泛型函数的用户可以使用任何生命周期。这在其主体内是一个问题。我们试图返回
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
我们可以轻松地这样写代码,而不需要编译器对函数体进行抱怨。但实际上,这是不必要的复杂操作。我们也可以简单地编写:
fn min<'a, T: Ord>(x: &'a T, y: &'a T) -> &'a T { … }
使用一个生命周期参数来处理所有内容。编译器能够推断出'a
作为调用现场参数引用的最小生命周期,因为我们为两个参数都使用了相同的生命周期名称。而这个生命周期恰好是我们需要的返回类型。
希望这回答了你的问题。 :)
祝好!
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 { ... }
语义上有区别吗?还是它们的行为完全相同? - Léo