通用函数为什么不需要生命周期?

7

这段代码无法编译,因为 Rust 要求必须添加生命周期。

fn firstNoLifetime(x: &str, y: &str) -> &str {
    return x;
}

因此,我们必须显式地添加生命周期,如下所示:
fn first<'a>(x: &'a str, y: &'a str) -> &'a str {
    return x;
}

那么我的问题是,没有生命周期参数的这个函数是如何编译的?
为什么通用函数不需要生命周期参数,这方面有什么注意事项吗?

fn first_generic<A>(x: A, y: A) -> A {
    return x;
}

我的假设是生命周期注释可以帮助编译器和借用检查器确定生命周期违规的根本原因,但在下面的代码中,Rust 能够在没有注释的情况下确定原因。那么我的假设是错误的吗?如果是,生命周期注释的目的是什么?

fn first_generic<A>(x: A, y: A) -> A {
    return x;
}

fn main() {
    let string1 = String::from("long string is long");
    let result: &str;
    {
        let string2 = String::from("xyz");
        result = first_generic(string1.as_str(), string2.as_str());
    }

    println!("The first string is: {}", result);
}

结果:

   |
10 |         result = first_generic(string1.as_str(), string2.as_str());
   |                                                  ^^^^^^^^^^^^^^^^ borrowed value does not live long enough
11 |     }
   |     - `string2` dropped here while still borrowed
12 | 
13 |     println!("The first string is: {}", result);
   |                                           ------ borrow later used here

1
简短的回答是,生命周期是类型本身的一部分,因此从调用点推断。这就是为什么有时会在泛型类型上看到生命周期限制,例如 where T: 'a - cdhowie
由于规范对生命周期省略有具体的规定,而泛型参数的推断则更加普遍。特别要注意的是,在您的泛型示例中,T根本不必是一个引用。 - cdhowie
但是对于 &str 类型,生命周期在泛型实例化期间不会变成必需品吗? - Willem D'Haeseleer
是的,但是调用者提供了包括生命周期在内的所有T - cdhowie
实际上,正如您在我的示例代码中看到的那样,寿命未在调用站点提供,通用和非通用调用站点是相同的。 - Willem D'Haeseleer
显示剩余2条评论
2个回答

3

当在函数签名中省略生命周期时,每个输入生命周期都变成了一个独立的匿名生命周期参数。因此,下面这个函数:

fn firstNoLifetime(x: &str, y: &str) -> &str {
    return x;
}

等同于这个:

fn firstNoLifetime<'a, 'b>(x: &'a str, y: &'b str) -> &'? str {
    return x;
}

那么返回值的生命周期是多久呢?在这里使用独立的生命周期没有意义,因为没有有用的引用可以满足该生命周期。

根据 生命周期省略 规则,当只有一个输入生命周期时,则该生命周期用于返回值。如果有多个输入生命周期,但其中一个是 &self&mut self,则使用 self 的生命周期作为返回值的生命周期。这些规则的原因是我们不希望生命周期推断基于函数的实现,因为实现的更改可能导致签名的更改,这通常会是一种破坏性的更改。

泛型函数之所以有效,是因为生命周期参数是引用类型的一部分。当您使用引用调用 first_generic 时,编译器必须找到与两个参数兼容的一个单一类型(包括找到公共生命周期),就像对于 first 一样。我们在 first_generic 中指定了两个参数具有相同类型 A(无论 A 是什么),因此无法发生像 firstNoLifetime 那样的错误,其参数具有不同的类型。


1

生命周期是类型本身的一部分

在您的第一个示例中,您将'a指定给所有xy和返回值。如果您只接收单个值作为引用,那么您就不需要传递生命周期说明符了,对吧?

fn first(x: &str) -> &str {
    x
}

这是因为编译器自己推断它们的生命周期。

回到你的问题,“为什么泛型函数不需要生命周期”,简短的答案是需要。只是在你提供的例子中不需要。为什么呢?

在你的第二个例子中,你表明 xy 和返回值必须与所有的 A 相同类型。提醒一下,生命周期是类型本身的一部分,在这里只有一个生命周期起作用,所以编译器可以自行推断。

我的假设是生命周期注释帮助编译器和借用检查器确定生命周期违规的根本原因,

是的,你的假设也是正确的。在这种情况下,生命周期注释确实帮助编译器和借用检查器确定生命周期违规的根本原因。

但是在下面的代码中,Rust 能够确定原因,而不需要注释。

如上所述,在这里只有一个生命周期起作用,所以 Rust 可以自行推断。然而,在某些较旧版本的 Rust 编译器中,这并不是真的(我记不清楚具体版本了,但那时,在泛型情况下,我们也必须指定生命周期注释)。


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