跳转至

Lifetimes

在Rust中,对象生命周期和所有权是紧密相关的。所有权规则是Rust的核心特性,也是Rust与其他语言最大的不同之处。

所有权(Ownership)

每一块内存数据都和一个变量名绑定,这个变量名就是这块内存数据的所有者。当所有者超出作用域时,这块内存数据就会被释放。在本节中,“变量”和“绑定”都是指通过let关键字声明的变量。

当使用=将一个变量赋值给另一个变量,或是将变量作为函数参数传递时,所有权会发生转移,此时有两种可能:

  • 值拷贝:原来的内存数据会被复制一份给新的变量,两个变量名绑定的内存数据不同。
  • 值移动:原来的内存数据会被重新绑定给新的变量,此时不能再通过原来的变量名访问内存数据。

默认情况下,Rust会采取值移动的方式进行赋值,除非对象的类型实现了Copy trait。大多数基本类型都实现了Copy trait。

fn main() {
    let a = vec![1, 2, 3]; // vec is not a primitive type, and it does not implement Copy trait
    let b = a;
    println!("{:?} {:?}", a, b); // Error; use of moved value: `a`
}

引用(Borrowing)

在Rust中,变量的引用有两种,即只读引用(&T)和可变引用(&mut T)。只读引用和可变引用都是指向原始变量的指针,但只读引用不允许修改原始变量的值,而可变引用允许修改原始变量的值。可变引用只能指向可变变量。需要使用解引用运算符*来访问引用指向的值。

fn main() {
    let const_var = 5;
    let mutable_ref = &mut const_var;
    // error[E0596]: cannot borrow `const_var` as mutable, as it is not declared as mutable
}

引用的生命周期是从引用声明开始到引用最后一次使用结束。关于引用的使用有如下限制:

  1. 在引用的生命周期内,可变引用和只读引用不能同时存在。

    fn main() {
        let mut owner = 5;
        let shared_borrow = &owner; // share_borrow starts here
        let mutable_borrow = &mut owner; // mutable_borrow starts here
        println!("{}", mutable_borrow); // Error
        // mutable_borrow ends here
        println!("{}", shared_borrow);
        // share_borrow ends here
    }
    
    fn main() {
        let mut owner = 5;
        let shared_borrow = &owner; // shared_borrow starts here
        println!("{}", shared_borrow);
        // shared_borrow ends here
        // mutable_borrow starts here
        let mutable_borrow = &mut owner; // mutable_borrow starts here
        println!("{}", mutable_borrow);
        // mutable_borrow ends here
    }
    
  2. 在可变引用的生命周期内,不能对同一变量声明新的可变引用。

    fn main() {
        let mut owner = 5;
        let mutable_borrow = &mut owner;
        let another_mutable_borrow = &mut owner;
        println!("{} {}", mutable_borrow, another_mutable_borrow);
        // error[E0499]: cannot borrow `owner` as mutable more than once at a time
    }
    
    fn main() {
        let mut owner = 5;
        let mutable_borrow = &mut owner;
        println!("{}", mutable_borrow);
        let another_mutable_borrow = &mut owner;
        println!("{}", another_mutable_borrow);
    }
    
  3. 在可变引用的生命周期内,不能访问原始变量或对原始变量进行赋值。

    fn main() {
        let mut owner = 5;
        let mutable_borrow = &mut owner;
        println!("{}", owner + *mutable_borrow);
        // error[E0503]: cannot use `owner` because it was mutably borrowed
    }
    
    fn main() {
        let mut owner = 5;
        let mutable_borrow = &mut owner;
        println!("{}", *mutable_borrow + owner); // 10
    }
    
  4. 可以用数据库中“共享锁”和“排他锁”的概念来理解只读引用和可变引用。只读引用相当于对原始变量加共享锁,可变引用相当于对原始变量加排他锁。对于同一块数据,共享锁可以同时存在,但排他锁只能有一个。

  5. 引用的生命周期不能超过原始变量的生命周期。

    struct Foo<'a> {
        x: Option<&'a u32>,
    }
    
    fn main() {
        let mut x = Foo { x: None };
        {
            let y = 0;
            x.x = Some(&y);
        } // Leaving the scope of y, y is dropped
        println!("{:?}", x.x) // error[E0597]: `y` does not live long enough
    }
    
    fn one(x: &i32) -> &i32 {
        let ret = *x + 1;
        &ret // error[E0515]: cannot return value referencing local variable `ret`
    } // Leaving the scope of ret, ret is dropped
    
    fn main() {
        println!("{}", *one(&5));
    }
    

生命周期

引用相当于指向一段内存空间的指针,在最后一次访问引用后,引用的生命周期就结束了,但在一些情况下需要继续保持引用的生命周期,比如在函数中返回引用。在这种情况下,需要使用引用标记来指定引用的生命周期。

fn main() {
    let reference_to_nothing = dangle();
    // error[E0106]: missing lifetime specifier
}

fn dangle() -> &String {
    let s = String::from("hello");

    &s // The lifetime of s is over, so this reference is dangling
}

引用标记可以用于泛型参数、函数参数、函数返回值,而不能用于引用声明。有相同引用标记的引用的生命周期相同。'static是一个特殊引用标记,表示引用的生命周期是整个程序的生命周期,即引用指向的内存空间在程序结束时才会被释放。所有指向字符串字面量的引用都是'static类型。

fn function<'a>() -> &'a str {}
fn function<'a>(x: &'a str) {}
fn function<'a>(x: &'a str) -> &'a str {}
fn function<'a>(x: &'a str, y: &'a str) -> &'a str {}
fn function<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {}
enum Enum<'a> {
    Variant1(&'a str),
    Variant2(&'a str) // exist as long as parent object exists
}
struct Struct<'a> {
    field: &'a str // exist as long as parent object exists
}
struct Struct<'a> {
    field: &'a str
}
trait New<'a> {
    fn new() -> Self;
}
impl<'a> New<'a> for Struct<'a> {
    fn new() -> Self {
        Struct { field: "hello" }
    }
}
fn function<T>(x: T) where for<'a> T: Fn(&'a str) -> &'a str {}
enum Enum<T> where for<'a> T: Fn(&'a str) -> &'a str { V(T) }
struct Struct<T> where for<'a> T: Fn(&'a str) -> &'a str { field: T }
impl<T> Struct<T> where for<'a> T: Fn(&'a str) -> &'a str {
    fn function(&self) -> &F { &self.field }
}

其中,where for有另一种写法

fn function<T>(x: T) where T: for<'a> Fn(&'a str) -> &'a str {}

forT进行了限制,表示T必须是一个函数,且这个函数的参数和生命周期必须和模板匹配。

struct Closure<F> {
    data: (u8, u16),
    func: F,
}

impl<F> Closure<F>
    where for<'a> F: Fn(&'a (u8, u16)) -> &'a u8,
{
    fn call(&self) -> &u8 {
        (self.func)(&self.data)
    }
}

fn do_it(data: &(u8, u16)) -> &u8 { &data.0 }

fn main() {
    let clo = Closure { data: (0, 1), func: do_it };
    println!("{}", clo.call());
}

不同引用标记的引用不能相互赋值,在作为函数返回值时,Rust也会检查引用标记是否匹配。可以用'a: 'b来约束'a的生命周期不短于'b的生命周期。

fn two_args<'a, 'b>(a: &'a mut i32, b: &'b i32) -> &'b mut i32 {
    *a += b;
    a // error: lifetime may not live long enough
}
fn main() {
    let mut a = 3;
    let b: &mut i32 = two_args(&mut a, &4);
    println!("{}", b);
}
fn two_args<'a, 'b>(a: &'a mut i32, b: &'b i32) -> &'a mut i32 {
    *a += b;
    a
}
fn main() {
    let mut a = 3;
    let b: &mut i32 = two_args(&mut a, &4);
    println!("{}", b);
}
fn two_args<'a: 'b, 'b>(a: &'a mut i32, b: &'b i32) -> &'b mut i32 {
    *a += b;
    a
}
fn main() {
    let mut a = 3;
    let b: &mut i32 = two_args(&mut a, &4);
    println!("{}", b);
}

通常情况下,函数声明中的引用标记可以省略,Rust会自动推断每个参数和返回值的引用标记

  1. 每个输出参数都会分配一个独立的引用标记。

    fn four_args(a: &i32, b: &i32, c: &i32, d: &i32) -> i32 {
        a + b + c + d
    }
    
    fn four_args<'a, 'b, 'c, 'd>(a: &'a i32, b: &'b i32, c: &'c i32, d: &'d i32) -> i32 {
        a + b + c + d
    }
    
  2. 如果只有一个输入参数,那么这个参数的引用标记就是(所有)返回值的引用标记。

    fn one_arg(a: &i32) -> &i32 {
        a
    }
    
    fn one_arg<'a>(a: &'a i32) -> &'a i32 {
        a
    }
    
  3. 当函数的某个参数为&self&mut self时,返回值的引用标记和&self的引用标记相同。

    struct Player {
        first_name: String,
        last_name: String,
    }
    
    impl Player {
        fn full_name(&self) -> String {
            format!("{} {}", self.first_name, self.last_name)
        }
    }
    
    struct Player {
        first_name: String,
        last_name: String,
    }
    
    impl Player {
        fn full_name<'a>(&'a self) -> String {
            format!("{} {}", self.first_name, self.last_name)
        }
    }
    
  4. 如果不满足上述条件,则需要显式指定引用标记。

    fn two_args(a: &mut i32, b: &i32) -> &mut i32 {
        *a += b;
        a // error[E0106]: missing lifetime specifier
    }
    fn main() {
        let mut a = 3;
        let b: &mut i32 = two_args(&mut a, &4);
        println!("{}", b);
    }
    
    fn two_args<'a, 'b>(a: &'a mut i32, b: &'b i32) -> &'a mut i32 {
        *a += b;
        a
    }
    fn main() {
        let mut a = 3;
        let b: &mut i32 = two_args(&mut a, &4);
        println!("{}", b);
    }
    

评论