rust - 生命周期学习与引用有效性

通过对《Rust 程序设计语言》,《通过例子学 Rust 中文版》以及令狐一冲老师对相关知识点的学习总结而成。

rust - 生命周期学习

  • 1 生命周期避免了悬垂引用
  • 2 借用检查器
  • 3 函数中的泛型生命周期
  • 4 函数签名中的生命周期标注
  • 5 结构体定义中的生命周期标注
  • 6 生命周期的三条规则

1 生命周期避免了悬垂引用

生命周期的主要目标是避免悬垂引用,它会导致程序引用了非预期引用的数据。

{					/***************** a ****************/
    let r;			/* r的生命周期自从该处定义开始, 到整个a生命周期结束 */

    {				/***************** b *****************/
        let x = 5;	/* 从x 定义到生命周期b结束为x的生命周期 */
        r = &x;
    }				/* 在该处x被调用drop方法释放,r对x的引用成为悬垂引用 */

    println!("r: {}", r);
}

运行之后会提示如下的错误:

rlk@rlk:lifetime$ cargo run 
   Compiling lifetime v0.1.0 (/home/rlk/work/learn_rust/lifetime)
error[E0597]: `x` does not live long enough
  --> src/main.rs:7:13
   |
7  |         r = &x;
   |             ^^ borrowed value does not live long enough
8  |     }
   |     - `x` dropped here while still borrowed
9  |
10 |     println!("r = {}", r);
   |                        - borrow later used here

For more information about this error, try `rustc --explain E0597`.
error: could not compile `lifetime` due to previous error
rlk@rlk:lifetime

如上面的错误所指出的,x的生命周期不够长,但是r对x的引用却还一直存在,最终导致了悬垂引用。

2 借用检查器

Rust 编译器有一个 借用检查器(borrow checker),它比较作用域来确保所有的借用都是有效的。
下面是上面的例子,但是更清晰的标注了r和x的生命周期

{
    let r;                // ---------+-- 'a
                          //          |
    {                     //          |
        let x = 5;        // -+-- 'b  |
        r = &x;           //  |       |
    }                     // -+       |
                          //          |
    println!("r: {}", r); //          |
}                         // ---------+

将上面的例子修改为如下所示就可以正常编译运行通过,

{
    let x = 5;            // ----------+-- 'b
                          //           |
    let r = &x;           // --+-- 'a  |
                          //   |       |
    println!("r: {}", r); //   |       |
                          // --+       |
} 

x的生命周期比r长,并且包括了r的生命周期,所以r可以正确的去引用x,当这段代码结束的时候,x和r都会通过drop方法被释放掉,所以也就不会产生悬垂引用的问题。

3 函数中的泛型生命周期

下面我们要实现一个返回两个字符串较长字符串的处理函数。

fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

在编译执行时会提示如下的错误:

rlk@rlk:lifetime$ cargo run
   Compiling lifetime v0.1.0 (/home/rlk/work/learn_rust/lifetime)
error[E0106]: missing lifetime specifier
 --> src/main.rs:1:33
  |
1 | fn longest(x: &str, y: &str) -> &str {
  |               ----     ----     ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
help: consider introducing a named lifetime parameter
  |
1 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
  |           ++++     ++          ++          ++

For more information about this error, try `rustc --explain E0106`.
error: could not compile `lifetime` due to previous error
rlk@rlk:lifetime$

借用检查器无法确定引用是否正确,因为它不知道 x 和 y 的生命周期是如何与返回值的生命周期相关联的。所以此时我们需要显著的去标注生命周期。

4 函数签名中的生命周期标注

生命周期标注并不改变任何引用的生命周期的长短。与当函数签名中指定了泛型类型参数后就可以接受任何类型一样,当指定了泛型生命周期后函数也能接受任何生命周期的引用。生命周期标注描述了多个引用生命周期相互的关系,而不影响其生命周期。
生命周期标注有着一个不太常见的语法:生命周期参数名称必须以撇号(')开头,其名称通常全是小写,类似于泛型其名称非常短。

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

函数签名表明对于某些生命周期 'a,函数会获取两个参数,他们都是与生命周期 'a 存在的一样长的字符串 slice。函数会返回一个同样也与生命周期 'a 存在的一样长的字符串 slice。它的实际含义是 longest 函数返回的引用的生命周期与传入该函数的引用的生命周期的较小者一致。这就是我们告诉 Rust 需要其保证的约束条件。记住通过在函数签名中指定生命周期参数时,我们并没有改变任何传入值或返回值的生命周期,而是指出任何不满足这个约束条件的值都将被借用检查器拒绝

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {
    let s = String::from("Hello World");

    {
        let t = String::from("beautiful");
        let result = longest(s.as_str(), t.as_str());
        println!("The longest string is {}", result);
    }

    println!("Hello, world!");
}

其运行结果为:

rlk@rlk:lifetime$ cargo run
   Compiling lifetime v0.1.0 (/home/rlk/work/learn_rust/lifetime)
    Finished dev [unoptimized + debuginfo] target(s) in 0.61s
     Running `target/debug/lifetime`
The longest string is Hello World
Hello, world!
rlk@rlk:lifetime$

但是如果将上面的测试代码修改为如下所示,则会出现错误。

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {
    let s = String::from("Hello World");

    let result;
    {
        let t = String::from("beautiful");
        result = longest(s.as_str(), t.as_str());
    }
    println!("The longest string is {}", result);

    println!("Hello, world!");
}

提示的错误如下所示:

rlk@rlk:lifetime$ cargo run
   Compiling lifetime v0.1.0 (/home/rlk/work/learn_rust/lifetime)
error[E0597]: `t` does not live long enough
  --> src/main.rs:15:38
   |
15 |         result = longest(s.as_str(), t.as_str());
   |                                      ^^^^^^^^^^ borrowed value does not live long enough
16 |     }
   |     - `t` dropped here while still borrowed
17 |     println!("The longest string is {}", result);
   |                                          ------ borrow later used here

For more information about this error, try `rustc --explain E0597`.
error: could not compile `lifetime` due to previous error
rlk@rlk:lifetime$

现在假如我们要实现一个longest函数,但是不论传入的是什么参数,我们都返回第一个参数,这个时候生命周期的标注就可以忽略掉不需要关注的参数部分。如下所示:

fn longest2<'a>(x: &'a str, y: &str) -> &'a str {
    x
}

fn main() {
    let s = String::from("Hello World");

    let result;
    {
        let t = String::from("beautiful");
        result = longest2(s.as_str(), t.as_str());
    }
    println!("The longest string is {}", result);

    println!("Hello, world!");
}

运行结果如下所示:

rlk@rlk:lifetime$ cargo run
warning: unused variable: `y`
 --> src/main.rs:9:29
  |
9 | fn longest2<'a>(x: &'a str, y: &str) -> &'a str {
  |                             ^ help: if this is intentional, prefix it with an underscore: `_y`
  |
  = note: `#[warn(unused_variables)]` on by default

warning: `lifetime` (bin "lifetime") generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/lifetime`
The longest string is Hello World
Hello, world!
rlk@rlk:lifetime$

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

#[derive(Debug)]
struct ImportantExcerpt<'a> {
    part: &'a str,
}

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 };

    println!("i = {:#?}", i);

    println!("Hello, world!");
}

其运行结果如下所示:

rlk@rlk:struct_lifetime$ cargo run
warning: field `part` is never read
 --> src/main.rs:3:5
  |
2 | struct ImportantExcerpt<'a> {
  |        ---------------- field in this struct
3 |     part: &'a str,
  |     ^^^^
  |
  = note: `#[warn(dead_code)]` on by default
  = note: `ImportantExcerpt` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis

warning: `struct_lifetime` (bin "struct_lifetime") generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/struct_lifetime`
i = ImportantExcerpt {
    part: "Call me Ishmael",
}
Hello, world!
rlk@rlk:struct_lifetime$

6 生命周期的三条规则

第一条规则是每一个是引用的参数都有它自己的生命周期参数。换句话说就是,有一个引用参数的函数有一个生命周期参数: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,说明是个对象的方法(method), 那么所有输出生命周期参数被赋予 self 的生命周期。第三条规则使得方法更容易读写,因为只需更少的符号。

你可能感兴趣的:(编程语言-rust,rust,生命周期与引用有效性,rust生命周期)