Rust-04

78 阅读6分钟

Rust会明确资源所有者,当我们进行let refX=refy时等赋值和传值时,资源的所有者转移了。这个在Rust中称为移动。

部分移动

可以通过move或者ref方式消费资源部分项,但是move后无法使用资源整体可以使用引用部分

fn main() {
    #[derive(Debug)]
    struct Person {
        name: String,
        age: u8,
    }

    let person = Person {
        name: String::from("Alice"),
        age: 20,
    };

    // `name` 从 person 中移走,但 `age` 只是引用
    let Person { name, ref age } = person;

    println!("The person's age is {}", age);

    println!("The person's name is {}", name);

    // 报错!部分移动值的借用:`person` 部分借用产生
    //println!("The person struct is {:?}", person);

    // `person` 不能使用,但 `person.age` 因为没有被移动而可以继续使用
    println!("The person's age from person struct is {}", person.age);
}

借用

多数情况下,我们更希望能访问数据,同时不取得其所有权。为实现这点,Rust 使用了借用(borrowing)机制。对象可以通过引用(&T)来传递,从而取代通过值(T)来传递。

编译器(通过借用检查)静态地保证了引用总是指向有效的对象。也就是说,当存在引用指向一个对象时,该对象不能被销毁。

ref

在通过 let 绑定来进行模式匹配或解构时,ref 关键字可用来创建结构体/元组的字段的引用。下面的例子展示了几个实例,可看到 ref 的作用:

#[derive(Clone, Copy)]
struct Point { x: i32, y: i32 }

fn main() {
    let c = 'Q';

    // 赋值语句中左边的 `ref` 关键字等价于右边的 `&` 符号。
    let ref ref_c1 = c;
    let ref_c2 = &c;

    println!("ref_c1 equals ref_c2: {}", *ref_c1 == *ref_c2);

    let point = Point { x: 0, y: 0 };

    // 在解构一个结构体时 `ref` 同样有效。
    let _copy_of_x = {
        // `ref_to_x` 是一个指向 `point` 的 `x` 字段的引用。
        let Point { x: ref ref_to_x, y: _ } = point;

        // 返回一个 `point` 的 `x` 字段的拷贝。
        *ref_to_x
    };

    // `point` 的可变拷贝
    let mut mutable_point = point;

    {
        // `ref` 可以与 `mut` 结合以创建可变引用。
        let Point { x: _, y: ref mut mut_ref_to_y } = mutable_point;

        // 通过可变引用来改变 `mutable_point` 的字段 `y`。
        *mut_ref_to_y = 1;
    }

    println!("point is ({}, {})", point.x, point.y);
    println!("mutable_point is ({}, {})", mutable_point.x, mutable_point.y);

    // 包含一个指针的可变元组
    let mut mutable_tuple = (Box::new(5u32), 3u32);
    
    {
        // 解构 `mutable_tuple` 来改变 `last` 的值。
        let (_, ref mut last) = mutable_tuple;
        *last = 2u32;
    }
    
    println!("tuple is {:?}", mutable_tuple);
}

生命周期

资源的生命周期和作用域并不相同,离开作用域借用的生命周期就结束了,但是生命周期会直到资源所有者被消毁。

// `i` 的生命周期最长,因为它的作用域完全覆盖了 `borrow1` 和
// `borrow2` 的。`borrow1` 和 `borrow2` 的周期没有关联,
// 因为它们各不相交。
fn main() {
    let i = 3; // Lifetime for `i` starts. ────────────────┐
    //                                                     │
    { //                                                   │
        let borrow1 = &i; // `borrow1` lifetime starts. ──┐│
        //                                                ││
        println!("borrow1: {}", borrow1); //              ││
    } // `borrow1 ends. ──────────────────────────────────┘│
    //                                                     │
    //                                                     │
    { //                                                   │
        let borrow2 = &i; // `borrow2` lifetime starts. ──┐│
        //                                                ││
        println!("borrow2: {}", borrow2); //              ││
    } // `borrow2` ends. ─────────────────────────────────┘│
    //                                                     │
}   // Lifetime ends. ─────────────────────────────────────┘

函数

排除省略(elision)的情况,带上生命周期的函数签名有一些限制:

  • 任何引用都必须拥有标注好的生命周期。
  • 任何被返回的引用都必须有和某个输入量相同的生命周期或是静态类型(static)。
  • 不允许没有输入的函数返回引用

方法

译注:方法一般是不需要标明生命周期的,因为 self 的生命周期会赋给所有的输出生命周期参数,详见 TRP

约束

就如泛型类型能够被约束一样,生命周期(它们本身就是泛型)也可以使用约束。: 字符的意义在这里稍微有些不同,不过 + 是相同的。注意下面的说明:

  1. T: 'a:在 T 中的所有引用都必须比生命周期 'a 活得更长。
  2. T: Trait + 'a:T 类型必须实现 Trait trait,并且在 T 中的所有引用都必须比 'a 活得更长。

强制转换

一个较长的生命周期可以强制转成一个较短的生命周期,使它在一个通常情况下不能工作的作用域内也能正常工作。强制转换可由编译器隐式地推导并执行,也可以通过声明不同的生命周期的形式实现。

// 在这里,Rust 推导了一个尽可能短的生命周期。
// 然后这两个引用都被强制转成这个生命周期。
fn multiply<'a>(first: &'a i32, second: &'a i32) -> i32 {
    first * second
}

// `<'a: 'b, 'b>` 读作生命周期 `'a` 至少和 `'b` 一样长。
// 在这里我们我们接受了一个 `&'a i32` 类型并返回一个 `&'b i32` 类型,这是
// 强制转换得到的结果。
fn choose_first<'a: 'b, 'b>(first: &'a i32, _: &'b i32) -> &'b i32 {
    first
}

fn main() {
    let first = 2; // 较长的生命周期

    {
        let second = 3; // 较短的生命周期

        println!("The product is {}", multiply(&first, &second));
        println!("{} is the first", choose_first(&first, &second));
    };
}

static

'static 生命周期是可能的生命周期中最长的,它会在整个程序运行的时期中存在。'static 生命周期也可被强制转换成一个更短的生命周期。有两种方式使变量拥有 'static 生命周期,它们都把数据保存在可执行文件的只读内存区:

  • 使用 static 声明来产生常量(constant)。
  • 产生一个拥有 &'static str 类型的 string 字面量。

看下面的例子,了解列举到的各个方法:

// 产生一个拥有 `'static` 生命周期的常量。
static NUM: i32 = 18;

// 返回一个指向 `NUM` 的引用,该引用不取 `NUM` 的 `'static` 生命周期,
// 而是被强制转换成和输入参数的一样。
fn coerce_static<'a>(_: &'a i32) -> &'a i32 {
    &NUM
}

fn main() {
    {
        // 产生一个 `string` 字面量并打印它:
        let static_string = "I'm in read-only memory";
        println!("static_string: {}", static_string);

        // 当 `static_string` 离开作用域时,该引用不能再使用,不过
        // 数据仍然存在于二进制文件里面。
    }

    {
        // 产生一个整型给 `coerce_static` 使用:
        let lifetime_num = 9;

        // 将对 `NUM` 的引用强制转换成 `lifetime_num` 的生命周期:
        let coerced_static = coerce_static(&lifetime_num);

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

    println!("NUM: {} stays accessible!", NUM);
}

省略

有些生命周期的模式太常用了,所以借用检查器将会隐式地添加它们以减少程序输入量和增强可读性。这种隐式添加生命周期的过程称为省略(elision)。在 Rust 使用省略仅仅是因为这些模式太普遍了。

下面代码展示了一些省略的例子。对于省略的详细描述,可以参考官方文档的生命周期省略

// `elided_input` 和 `annotated_input` 事实上拥有相同的签名,
// `elided_input` 的生命周期会被编译器自动添加:
fn elided_input(x: &i32) {
    println!("`elided_input`: {}", x)
}

fn annotated_input<'a>(x: &'a i32) {
    println!("`annotated_input`: {}", x)
}

// 类似地,`elided_pass` 和 `annotated_pass` 也拥有相同的签名,
// 生命周期会被隐式地添加进 `elided_pass`:
fn elided_pass(x: &i32) -> &i32 { x }

fn annotated_pass<'a>(x: &'a i32) -> &'a i32 { x }

fn main() {
    let x = 3;

    elided_input(&x);
    annotated_input(&x);

    println!("`elided_pass`: {}", elided_pass(&x));
    println!("`annotated_pass`: {}", annotated_pass(&x));
}