为什么泛型函数不需要生命周期

why are lifetimes not required on generic functions

此代码无法编译,因为 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

Lifetimes are part of type itself

在您的第一个示例中,您为所有 xy 和 return 值指定了相同的值,即 'a。如果您收到单一值作为参考,您就不必传递生命周期说明符对吗?

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

这是因为编译器会自行推断两者的生命周期。

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

在您的第二个示例中,您表示 xy 和 return 值必须具有相同的类型,因为它们都是 A。提醒生命周期是类型本身的一部分,这里只有一个生命周期起作用,因此编译器可以自行推断它。

My assumption was that lifetime annotations help the compiler and borrow checker determine the root cause of lifetime violations,

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

but in the code below rust was able to determine the cause without annotations

如上所述,这里只有一个生命周期在起作用,因此 Rust 能够自行推断。然而,在某些旧版本的 rust 编译器中情况并非如此(我不记得确切的版本,但当时,我们也必须在您的通用情况下指定生命周期注释)

当函数签名中的生命周期被省略时,每个输入生命周期都会成为一个不同的匿名生命周期参数。因此,这:

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

相当于:

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

return 值的生命周期是多少?在这里使用不同的生命周期没有意义,因为没有有用的引用可以满足该生命周期。

根据 lifetime elision 规则,当存在 单个 输入生命周期时,该生命周期将用于 return 值。如果有多个输入生命周期,但其中之一是 &self&mut self,则 self 的生命周期用于 return 值。这些规则的原因是我们不希望生命周期推断基于函数的 实现 ,因为实现中的更改可能导致签名更改,这通常是一个重大的变化。

泛型函数起作用的原因是生命周期参数是引用类型的一部分。当您使用引用调用 first_generic 时,编译器必须找到一个与两个参数兼容的 single 类型(包括找到一个共同的生命周期),就像它本来的方式一样到 first。我们用 firstNoLifetime 得到的错误不会发生在 first_generic 上,因为 firstNoLifetime 的参数有不同的类型,这不会发生在 first_generic 上,因为我们指定两个参数具有相同的类型输入 A(无论 A 是什么)。