函数中的泛型生命周期

  1. fn longest(x: &str, y: &str) -> &str {
  2. if x.len() > y.len() {
  3. x
  4. } else {
  5. y
  6. }
  7. }
  1. $ cargo run
  2. Compiling chapter10 v0.1.0 (file:///projects/chapter10)
  3. error[E0106]: missing lifetime specifier
  4. --> src/main.rs:9:33
  5. |
  6. 9 | fn longest(x: &str, y: &str) -> &str {
  7. | ---- ---- ^ expected named lifetime parameter
  8. |
  9. = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
  10. help: consider introducing a named lifetime parameter
  11. |
  12. 9 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
  13. | ++++ ++ ++ ++
  14. For more information about this error, try `rustc --explain E0106`.
  15. error: could not compile `chapter10` due to previous error

提示文本揭示了返回值需要一个泛型生命周期参数,因为 Rust 并不知道将要返回的引用是指向 x 或 y。事实上我们也不知道,因为函数体中 if 块返回一个 x 的引用而 else 块返回一个 y 的引用!

生命周期注解语法

生命周期注解并不改变任何引用的周期长短。当指定了泛型生命周期后函数也能接受任何生命周期的引用。生命周期注解描述了多个引用生命周期相互的关系,而不影响其生命周期。
生命周期注解有一个不太常见的语法:生命周期参数名称必须以撇号开头,其名称通常全是小写。'a是大多数人默认使用的名称。生命周期参数注解位于引用的 **&**之后,并有一个空格来将引用类型与生命周期注解分隔开。

  1. &i32 // 引用
  2. &'a i32 // 带有显式生命周期的引用
  3. &'a mut i32 // 带有显式生命周期的可变引用

函数签名中的生命周期注解

  1. fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
  2. if x.len() > y.len() {
  3. x
  4. } else {
  5. y
  6. }
  7. }

现在函数签名表明对于某些生命周期 'a,函数会获得两个参数,他们都是与生命周期 'a存在的一样长的字符串 slice。它的实际含义是 **longest**函数返回的引用生命周期与传入该函数的引用生命周期的较小者一致。
通过在函数签名中指定生命周期参数时,并没有改变任何传入值或返回值的生命周期,而是指出任何不满足这个约束条件的值都将被借用检查器拒绝。注意 longest函数并不需要知道 xy具体会存在多久,而只需要知道有某个可以被 'a替代的作用域将会满足这个签名。
当具体的引用被传递给 longest时,被 'a所替代的具体生命周期是 x 的作用域与 y 的作用域相重叠的那一部分。也就是说,是泛型生命周期 'a的具体生命周期等同于 xy的生命周期中较小的那一个。

  1. fn main() {
  2. let string1 = String::from("long string is long");
  3. {
  4. let string2 = String::from("xyz");
  5. let result = longest(string1.as_str(), string2.as_str());
  6. println!("The longest string is {}", result);
  7. }
  8. }
  9. fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
  10. if x.len() > y.len() {
  11. x
  12. } else {
  13. y
  14. }
  15. }

string1 直到外部作用域结束都是有效的,string2 则在内部作用域中是有效的,而 result 则引用了一些直到内部作用域结束都是有效的值。借用检查器认可这些代码;它能够编译和运行,并打印出 The longest string is long string is long。

  1. fn main() {
  2. let string1 = String::from("long string is long");
  3. let result;
  4. {
  5. let string2 = String::from("xyz");
  6. result = longest(string1.as_str(), string2.as_str());
  7. }
  8. println!("The longest string is {}", result);
  9. }
  10. fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
  11. if x.len() > y.len() {
  12. x
  13. } else {
  14. y
  15. }
  16. }
  1. $ cargo run
  2. Compiling chapter10 v0.1.0 (file:///projects/chapter10)
  3. error[E0597]: `string2` does not live long enough
  4. --> src/main.rs:6:44
  5. |
  6. 6 | result = longest(string1.as_str(), string2.as_str());
  7. | ^^^^^^^^^^^^^^^^ borrowed value does not live long enough
  8. 7 | }
  9. | - `string2` dropped here while still borrowed
  10. 8 | println!("The longest string is {}", result);
  11. | ------ borrow later used here
  12. For more information about this error, try `rustc --explain E0597`.
  13. error: could not compile `chapter10` due to previous error

错误表明为了保证 println!中的 result 是有效的,string2 需要直到外部作用域结束都是有效的。Rust 知道这些是因为(longest)函数的参数和返回值都使用了相同的生命周期参数 'a

深入理解生命周期

  1. fn longest<'a>(x: &'a str, y: &str) -> &'a str {
  2. x
  3. }

当从函数返回一个引用,返回值的生命周期参数需要与一个参数的生命周期参数相匹配。如果返回的引用 没有 指向任何一个参数,那么唯一的可能就是它指向一个函数内部创建的值,它将会是一个悬垂引用,因为它将会在函数结束时离开作用域。

  1. fn main() {
  2. let string1 = String::from("abcd");
  3. let string2 = "xyz";
  4. let result = longest(string1.as_str(), string2);
  5. println!("The longest string is {}", result);
  6. }
  7. fn longest<'a>(x: &str, y: &str) -> &'a str {
  8. let result = String::from("really long string");
  9. result.as_str()
  10. }
  1. $ cargo run
  2. Compiling chapter10 v0.1.0 (file:///projects/chapter10)
  3. error[E0515]: cannot return reference to local variable `result`
  4. --> src/main.rs:11:5
  5. |
  6. 11 | result.as_str()
  7. | ^^^^^^^^^^^^^^^ returns a reference to data owned by the current function
  8. For more information about this error, try `rustc --explain E0515`.
  9. error: could not compile `chapter10` due to previous error

出现的问题是 result 在 longest 函数的结尾将离开作用域并被清理,而我们尝试从函数返回一个 result 的引用。无法指定生命周期参数来改变悬垂引用,而且 Rust 也不允许我们创建一个悬垂引用。在这种情况,最好的解决方案是返回一个有所有权的数据类型而不是一个引用,这样函数调用者就需要负责清理这个值了。

结构体定义中的生命周期注解

  1. struct ImportantExcerpt<'a> {
  2. part: &'a str,
  3. }
  4. fn main() {
  5. let novel = String::from("Call me Ishmael. Some years ago...");
  6. let first_sentence = novel.split('.').next().expect("Could not find a '.'");
  7. let i = ImportantExcerpt {
  8. part: first_sentence,
  9. };
  10. }

这个结构体有一个字段,存放了一个字符串 slice,必须在结构体名称后面的尖括号中声明泛型生命周期参数,以便在结构体定义中使用生命周期参数。这个注解意味着 ImportantExcerpt 的实例不能比其 part 字段中的引用存在的更久。如果存在更久,就会出现垂直引用。

生命周期省略

每一个引用都有一个生命周期,而且需要为那些使用了引用的函数或结构体指定生命周期。

  1. fn first_word(s: &str) -> &str {
  2. let bytes = s.as_bytes();
  3. for (i, &item) in bytes.iter().enumerate() {
  4. if item == b' ' {
  5. return &s[0..i];
  6. }
  7. }
  8. &s[..]
  9. }
  10. fn main() {
  11. let my_string = String::from("hello world");
  12. // first_word works on slices of `String`s
  13. let word = first_word(&my_string[..]);
  14. let my_string_literal = "hello world";
  15. // first_word works on slices of string literals
  16. let word = first_word(&my_string_literal[..]);
  17. // Because string literals *are* string slices already,
  18. // this works too, without the slice syntax!
  19. let word = first_word(my_string_literal);
  20. }

这个函数没有生命周期注解却能编译是由于一些历史原因:在早期版本的 Rust 中,的确是不能编译的。每一个引用都必须有明确的生命周期。

  1. fn first_word<'a>(s: &'a str) -> &'a str {

之后,Rust 团队发现需要加生命周期的场景是可预测的并且遵循几个明确的模式。接着 Rust 团队就把这些模式编码写进 Rust 编译器中。
被编码进 Rust 引用分析的模式被称为生命周期省略规则。如果 Rust 在明确遵守这些规则的前提下变量的生命周期仍然是模棱两可的话,它不会猜测剩余引用的生命周期应该是什么,会抛出一个错误,这可以通过增加对应引用之间相联系的生命周期注解来解决。

  • 输入生命周期:函数或方法的参数的生命周期
  • 输出生命周期:返回值的生命周期

编译器采用三条规则来判断引用何时不需要明确的注解:

  • 每一个引用都有它自己的生命周期参数

    有一个引用参数的函数有一个生命周期参数 fn foo<'a>(x: &'a i32),有两个引用参数的函数有两个不同的生命周期参数 fn foo<'a, 'b>(x: &'a i32, y: &'b i32)

  • 如果只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数

    fn foo<'a>(x: &'a i32) -> &'a i32

  • 如果方法有多个输入生命周期参数并且其中一个参数是 **&self****&mut self**,说明是个对象的方法,那么所有输出生命周期参数被赋予**self**的生命周期

    1. fn first_word(s: &str) -> &str {

    根据第一条规则,签名变成这样:

    1. fn first_word(s: &'a str) -> & str {

    根据第二条规则,只有一个输入生命周期参数,所以其生命周期赋予所有的输出:

    1. fn first_word(s: &'a str) -> &'a str {

    再看一个例子:

    1. fn longest(x: &str, y: &str) -> &str {

    根据第一条规则:

    1. fn longest(x: &'a str, y: &'b str) -> &str {

    但它不适合第二条规则和第三条规则,所以就报错:编译器使用所有已知的生命周期省略规则,仍不能计算出签名中所有引用的生命周期。

    方法定义中的生命周期注解

    实现方法时,结构体字段的生命周期必须总是在impl关键字之后声明并在结构体名称之后被使用,因为这些生命周期是结构体类型的一部分呢。
    impl块里的方法签名中,引用可能与结构体字段中的引用相关联,也可能是独立的。另外,生命周期省略规则也经常让我们无需在方法签名中使用生命周期注解。 ```rust struct ImportantExcerpt<’a> { part: &’a str, }

impl<’a> ImportantExcerpt<’a> { fn level(&self) -> i32 { 3 } }

// impl<’a> ImportantExcerpt<’a> { // fn announce_and_return_part(&self, announcement: &str) -> &str { // println!(“Attention please: {}”, announcement); // self.part // } // }

fn main() { let novel = String::from(“Call me Ishmael. Some years ago…”); let first_sentence = novel.split(‘.’).next().expect(“Could not find a ‘.’”); let i = ImportantExcerpt { part: first_sentence, }; }

  1. `impl` 之后和类型名称之后的生命周期参数是必要的,不过因为第一条生命周期规则我们并不必须标注`self`引用的生命周期。
  2. ```rust
  3. struct ImportantExcerpt<'a> {
  4. part: &'a str,
  5. }
  6. impl<'a> ImportantExcerpt<'a> {
  7. fn announce_and_return_part(&self, announcement: &str) -> &str {
  8. println!("Attention please: {}", announcement);
  9. self.part
  10. }
  11. }
  12. fn main() {
  13. let novel = String::from("Call me Ishmael. Some years ago...");
  14. let first_sentence = novel.split('.').next().expect("Could not find a '.'");
  15. let i = ImportantExcerpt {
  16. part: first_sentence,
  17. };
  18. }

上述符合第三条生命周期省略规则:其中一个参数是&self,返回值类型被赋予&self的生命周期。

静态生命周期

'static,其生命周期能够存活于整个程序期间。

  1. let s: &'static str = "I have a static lifetime.";

这个字符串的文本被直接储存在程序的二进制文件中而这个文件总是可用的。因此所有的字符串字面值都是'static的。

结合泛型类型参数、trait bounds 和生命周期

  1. fn main() {
  2. let string1 = String::from("abcd");
  3. let string2 = "xyz";
  4. let result = longest_with_an_announcement(
  5. string1.as_str(),
  6. string2,
  7. "Today is someone's birthday!",
  8. );
  9. println!("The longest string is {}", result);
  10. }
  11. use std::fmt::Display;
  12. fn longest_with_an_announcement<'a, T>(
  13. x: &'a str,
  14. y: &'a str,
  15. ann: T,
  16. ) -> &'a str
  17. where
  18. T: Display,
  19. {
  20. println!("Announcement! {}", ann);
  21. if x.len() > y.len() {
  22. x
  23. } else {
  24. y
  25. }
  26. }
  27. // Announcement! Today is someone's birthday!
  28. // The longest string is abcd

ann的类型是泛型 T,可以被放入任何实现了 where从句中指定的Displaytrait 的类型。这个额外的参数会使用{}打印,也就是为什么Displaytrait bound 是必须的。因为生命周期也是泛型,所以生命周期参数'a和泛型类型参数T都位于函数名后的同一尖括号列表中。