借用

Rust语言体用了 Borrowing 的概念,可以让Rust变量其他编程语言那样随意改变变量的值。

借用(Borrowing):顾名思义,即一个变量借用另一个变量的资源所有权。

不可变的借用

符号 & 表示对资源的借用。

let y = &x          // y借用x的资源访问权。y引用了资源。
println!("{:?}",y)  // x的所有权没有转移。但已经可以通过y访问x的资源。

y被称为资源的引用。 当引用变量离开作用域,即释放对资源的引用。

Rust 默认 & 是一种不可变的借用。即借用所有权的变量不可以修改资源的值。

fn main() {
    let x: Vec<i32> = vec!(1, 2, 3);
    let y = &x;    // y借用x对资源的所有权。vec的所有权仍然是x的。
    println!("x={:?}, y={:?}", x, y);
}

&x即对x的Borrowing。这不会发生所有权实际的转移。所以println没有编译异常。

不可变引用类型

借用到资源引用的变量,是一种特殊的类型。不再是资源原始的类型了。

let y = &x  // y借用x的资源访问权。y只是引用了资源。

x的类型为T,则&x的类型为&Ty的类型为&T。这是一个引用类型

需要有引用类型这个概念,这可以帮我们更好的理解Rust。

对于代码:

let a = 100;
let y = &a;

a的类型为i32,y的类型为&i32。y不可变,即y不能再与其他引用绑定。

对于代码:

let b = 200;
let mut y = &b;

b的类型为i32,y的类型为&i32。y可变,即y可以与其他引用绑定。

例:y可以与不同引用类型绑定。

fn main() {
    let a = 100;
    let b = 200;
    let mut y = &a;
    y = &b;
    println!("{:?},{:?}", a,y);
}

对于let mut y = &a;该语句会引发编译告警,但编译可通过。编译器认为这句语句没必要。直接写成let mut y = &b;即可。可以看到Rust编译器非常智能。

此处只是为了演示,真实场景不太可能写出这样的语句。

可变的借用

Rust也提供了一种可变资源的借用。

使用 &mut 符号表示对可变资源的引用。
若需要访问可变资源的引用,必须在变量前使用*来访问资源。

例:使用可变引用。

fn main() {
    let mut x: i32 = 100;

    // 可变借用
    let y: &mut i32 = &mut x;

    // y的类型是&mut i32,无法使用'+=',*y指向实际资源。使用'+='修改资源值。
    *y += 100;

    println!("{}", *y);    // 打印输出 200 。
    // 或者,可以写成。 println!("{}", y);
}

&mut i32 可以看作是一种可变i32的引用类型。

所以let y: &mut i32 = &mut x
可简写为:let y = &mut x

这段代码最后并没有直接访问x,而是打印输出的y。
为什么这段代码不直接写println!("{:?}", x);这是有原因的。

若上面的例子写成:

fn main() {
    let mut x: i32 = 100;
    let y: &mut i32 = &mut x;
    *y += 100;
    println!("{}", x);
}

编译报错。 为什么? 要解决这个问题,需要理解如下规则。

Rust的借用规则:

  1. 同一个作用域中,一个资源只有一个可变借用(&mut T),但拥有可变借用(&mut T)后就不能有不可变借用(&T)。
  2. 同一个作用域中,一个资源可以有多个不可变借用(&T),但拥有不可变借用(&T)后就不能有可变借用(&mut T)。
  3. 借用在离开作用域后释放。
  4. 可变借用(&mut T)释放前不可访问原变量。
  5. 若变量已经被借用,则变量的所有权不可再被move。

对于规则1、2:

简单来说,可变借用与不可变借用在同一作用域是互斥的,且可变结合两两互斥。

借用规则非常像“读写锁”。
即同一时刻,同一资源,只能拥有一个“写锁”,或只能拥有多个“读锁”,不允许“写锁”和“读锁”在同一时刻同时出现。

  • 可变引用(&mut T)相当于写锁。
  • 不可变引用(&T)相当于读锁。

这是数据读写过程中保障一致性的典型做法。Rust在编译中完成借用的检查,保证了程序在运行时不会出错。

对于规则3,4:

简单来说就是:归还借用,借走不碰。

借来的东西,总是要还的。一定要注意应该在何处何时正确的“归回”引用。

东西一旦借给别人使用,自己触碰不到了。所以也无法访问。

对于规则5:

别人正在用的东西,我自己也不该去动。

所以,对于上面代码若需要修改正确,则需要y释放掉可变引用。简单的做法就是,让y提前在一个作用域中释放掉。

fn main() {
    let mut x: i32 = 100;
    {
        let y: &mut i32 = &mut x;
        *y += 100;           // 修改 y 指定的资源。
    }                        // 释放可变引用。
    println!("{}", x);       // 该作用域汇总,x不再有其他可变引用。所以可以访问。
}

总结借用的可变性

Borrowing 分为 不可变引用(&T)与 可变引用(&mut T)。

“不可变引用”是只读的,不可更新被引用的内容。且不可变变量在同一个作用域可以有多个不可变借用。

例:

fn main() {
    let x: Vec<i32> = vec!(1i32, 2, 3);

    //可同时有多个不可变借用
    let y = &x;
    let z = &x;
    let m = &x;

    //ok
    println!("{:?}, {:?}, {:?}, {:?}", x, y, z, m);
}

使用“可变引用”的条件比较苛刻。同一个作用域中,可变变量只能有一个可变引用(&mut T),且被借用的变量本身必须有可变性。若可变借用必须释放所有权,否则原变量无法访问。

fn main() {
    //源变量x可变性
    let mut x: Vec<i32> = vec!(1i32, 2, 3);

    let y = &mut x;    // 一个可变借用。
    // let z = &mut x; // 只能有一个可变借用错误,所以该语句会编译错误。
    y.push(100);

    //ok
    println!("{:?}", y);

    // println!("{:?}", x); // 错误,可变借用y未释放,原变量x不可访问。
}  // 函数结束,y释放销毁。

关于可变借用有一些技巧:

  • 使用 { } 语句块可以在函数内回收被借走的所有权。
  • 原始变量被可变借用后不可再访问,但可变借用的变量依然可以继续被借用。

例:

fn main() {
    let mut x: Vec<i32> = vec!(1i32, 2, 3);

    //更新数组
    //push中对数组进行了可变借用,并在push函数退出时销毁这个借用
    x.push(10);

    {
        //可变借用1
        let mut y = &mut x;
        y.push(100);

        //可变借用2,注意:此处是对y的借用,不可再对x进行借用,
        //因为y在此时依然存活。
        let z = &mut y;
        z.push(1000);

        println!("{:?}", z); //打印: [1, 2, 3, 10, 100, 1000]
    } //y和z在此处被销毁,并释放借用。

    //访问x正常
    println!("{:?}", x); //打印: [1, 2, 3, 10, 100, 1000]
}

总结

  • 借用不改变内存的所有者(Owner),借用只是对源内存的临时引用。
  • 在借用周期内,借用方可以读写这块内存,所有者被禁止读写内存;且所有者保证在有“借用”存在的情况下,不会释放或转移内存。
  • 失去所有权的变量不可以被借用(访问)。
  • 在租借期内,内存所有者保证不会释放/转移/可变租借这块内存,但如果是在非可变租借的情况下,所有者是允许继续非可变租借出去的。
  • 借用周期满后,所有者收回读写权限.
  • 借用周期小于被借用者(所有者)的生命周期。

辨析:Rust为什么需要借用?

如果Rust没有借用而只拥有所有权的概念,会有什么问题?

存在一个函数:该函数的参数类型没有实现Copy特性,现在需要对传入的参数进行一些计算。

例:计算动态数组的长度。

fn main() {
    let v = vec![1,2,3];
    let v_len = length(v);
    println!("{:?}", v_len);
}

fn length(vec: Vec<i32>) -> usize {
    vec.len()
}

这段代码没有任何问题。输出 3 。
但是,在如果在length函数调用后仍需要使用变量v,则编译出错。

fn main() {
    let v = vec![1,2,3];
    let v_len = length(v);
    println!("{:?}", v_len);
    println!("{:?}", v);    // 添加的这一行编译报错。
}

fn length(vec: Vec<i32>) -> usize {
    vec.len()
}

由于最初v的所有权被传入length函数的参数vec中。对着length函数调用完毕,所有权没有从函数中传回来,所以导致v对自定义的所有权丢失。

若没有借用的概念,如何解决所有权丢失的问题呢?即通过返回值来返回所有权。

fn main() {
    let v = vec![1,2,3];
    let (v_len,v) = length(v);  // 变量v重新获取资源的所有权。
    println!("{:?}", v_len);
    println!("{:?}", v);
}

fn length(vec: Vec<i32>) -> (usize,Vec<i32>) {
    (vec.len(),vec)    // 将资源的所有权转移回去。
}

这种方法实在太累赘。使用Rust的借用,可以更好的解决所有权丢失的问题。

fn main() {
    let v = vec![1,2,3];
    let v_len = length(&v);

    println!("{:?}", v_len);    // 打印 3
    println!("{:?}", v);        // 打印 [1,2,3]
}

fn length(vec: &Vec<i32>) -> usize {
    vec.len()
}

当然,不止这些好处。

results matching ""

    No results matching ""