Skip to content

Latest commit

 

History

History
213 lines (161 loc) · 7.16 KB

14.生命周期.md

File metadata and controls

213 lines (161 loc) · 7.16 KB

生命周期

生命周期用于“借用检查”保证所有的借用都是有效的。

函数中的生命周期注解

示例1:

fn main() {
    let string1 = String::from("long string is long");
    let result;
    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
    }
    println!("The longest string is {}", result);
}

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

报错:

error[E0597]: `string2` does not live long enough
  --> src/main.rs:16:44
   |
16 |         result = longest(string1.as_str(), string2.as_str());
   |                                            ^^^^^^^ borrowed value does not live long enough
17 |     }
   |     - `string2` dropped here while still borrowed
18 |     println!("The longest string is {}", result);
   |                                          ------ borrow later used here

改进:

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

示例2: 当从函数返回一个引用,返回值的生命周期参数需要与一个参数的生命周期参数相匹配。如果返回的引用 没有 指向任何一个参数,那么唯一的可能就是它指向一个函数内部创建的值,它将会是一个悬垂引用,因为它将会在函数结束时离开作用域。尝试考虑这个并不能编译的 longest 函数实现:

fn longest<'a>(x: &str, y: &str) -> &'a str {
    let result = String::from("really long string");
    result.as_str()
}

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

生命周期语法是用于将函数的多个参数与其返回值的生命周期进行关联的。一旦他们形成了某种关联,Rust 就有了足够的信息来允许内存安全的操作并阻止会产生悬垂指针亦或是违反内存安全的行为。

生命周期的省略

省略规则:

函数或方法的参数的生命周期被称为 输入生命周期(input lifetimes),而返回值的生命周期被称为 输出生命周期(output lifetimes)。

编译器采用三条规则来判断引用何时不需要明确的注解。第一条规则适用于输入生命周期,后两条规则适用于输出生命周期。如果编译器检查完这三条规则后仍然存在没有计算出生命周期的引用,编译器将会停止并生成错误。这些规则适用于 fn 定义,以及 impl 块。

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

静态生命周期

这里有一种特殊的生命周期值得讨论:'static,其生命周期能够存活于整个程序期间。所有的字符串字面值都拥有 'static 生命周期,我们也可以选择像下面这样标注出来:

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

生命周期的父子约束

struct T {
    member: i32,
}

// 'a 是 'b 的“子类”,'a 的生命周期必须大于等与 'b
fn test<'a, 'b>(arg: &'a T) -> &'b i32 where 'a: 'b{
    &arg.member
}

注:

  1. 如果泛型约束中有 where T: 'a 之类的条件,则类型 T 的所有生命周期参数必须大于等于 'a
  2. T: Trait + 'a: Type T must implement trait Trait and all references in T must outlive 'a。
  3. 若是有 where T: 'static 约束,则类型 T 要么不包含任何引用,要么可以有指向 'static 的借用指针。
use std::fmt::Debug; // Trait to bound with.

#[derive(Debug)]
struct Ref<'a, T: 'a>(&'a T);
// `Ref` contains a reference to a generic type `T` that has
// an unknown lifetime `'a`. `T` is bounded such that any
// *references* in `T` must outlive `'a`. Additionally, the lifetime
// of `Ref` may not exceed `'a`.

// A generic function which prints using the `Debug` trait.
fn print<T>(t: T) where
    T: Debug {
    println!("`print`: t is {:?}", t);
}

// Here a reference to `T` is taken where `T` implements
// `Debug` and all *references* in `T` outlive `'a`. In
// addition, `'a` must outlive the function.
fn print_ref<'a, T>(t: &'a T) where
    T: Debug + 'a {
    println!("`print_ref`: t is {:?}", t);
}

fn main() {
    let x = 7;
    let ref_x = Ref(&x);

    print_ref(&ref_x);
    print(ref_x);
}

结构体中的生命周期注解

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

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

方法中的生命周期注解

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

这里有两个输入生命周期,所以 Rust 应用第一条生命周期省略规则并给予 &self 和 announcement 他们各自的生命周期。接着,因为其中一个参数是 &self,返回值类型被赋予了 &self 的生命周期,这样所有的生命周期都被计算出来了。

生命周期省略的弊端

fn get_str(s: &String) -> &str {
    println!("{}", s);
    "hello word"
}

fn main() {
    let x;
    {
        let c = String::from("hello");
        x = get_str(&c);
    }
    println!("{}", x);
}

报错

error[E0597]: `c` does not live long enough
  --> src/main.rs:16:21
   |
16 |         x = get_str(&c);
   |                     ^^ borrowed value does not live long enough
17 |     }
   |     - `c` dropped here while still borrowed
18 |         println!("{}", x);
   |                        - borrow later used here

改正

fn get_str(s: &String) -> &'static str {
    println!("{}", s);
    "hello word"
}