Skip to content

12.3 借用规则

关于借用指针,有以下几个规则:

  • 借用指针不能超过值的生命周期 (expected lifetime parameter,编译器提示缺少生命周期参数,生命周期是我们后续学习的内容)

  • &mut型借用只能指向本身具有 mut 修饰的变量,对于只读变量,不可以有&mut型借用。

  • &mut型借用指针存在的时候,被借用的变量本身会处于“冻结”状态,否则会出现针对同一个值的两个可读写入口的存在。

  • 如果只有&型借用指针,那么能同时存在多个;如果存在&mut型借用指针,那么只能存在一个;如果同时有其他的&或者&mut型借用指针存在,那么会出现编译错误。

总结: 一个值在任意给定的时间,要么只能有一个活跃的可变引用,要么只能有多个不可变引用。

借用指针只能临时地拥有对这个变量读或写的权限,没有义务管理这个变量的生命周期

示例如下:

rust
// 这里的参数采用的“引用传递”,意味着实参本身并未丢失对内存的管理权
fn borrow_semantics(v : &Vec<i32>) {

    // 打印参数占用空间的大小,在 64 位系统上,结果为 8,表明该指针与普通裸指针的内部表示方法相同
    println!("size of param: {}", std::mem::size_of::<&Vec<i32>>());
    for item in v {
        print!("{} ", item);
    }
    println!("");
}

// 这里的参数采用的“值传递”,而 Vec 没有实现 Copy trait,意味着它将执行 move 语义
fn move_semantics(v : Vec<i32>) {

    // 打印参数占用空间的大小,结果为 24,表明实参中栈上分配的内存空间复制到了函数的形参中
    println!("size of param: {}", std::mem::size_of::<Vec<i32>>());
    for item in v {
        print!("{} ", item);
    }
    println!("");
}

fn main() {
    let array = vec![1, 2, 3];

    // 需要注意的是,如果使用引用传递,不仅在函数声明的地方需要使用&标记
    // 函数调用的地方同样需要使用&标记,否则会出现语法错误
    // 这样设计主要是为了显眼,不用去阅读该函数的签名就知道这个函数调用的时候发生了什么
    // 而小数点方式的成员函数调用,对于 self 参数,会“自动转换”,不必显式借用,这里有个区别
    borrow_semantics(&array);

    // 在使用引用传递给上面的函数后,array 本身依然有效,我们还能在下面的函数中使用
    move_semantics(array);

    // 在使用 move 语义传递后,array 在这个函数调用后,它的生命周期已经完结
}

在这里给大家提个醒:一般情况下,函数参数使用引用传递的时候,不仅在函数声明这里要写上类型参数,在函数调用这里也要显式地使用引用运算符。 但是,有一个例外,那就是当参数为self &self &mut self等时,若使用小数点语法调用成员方法,在函数调用这里不能显式写出借用运算符。 以常见的 String 类型来举例:

rust
fn main() {
    // 创建了一个可变的 String 类型实例
    let mut x : String = "hello".into();

    // 调用 len(&self) -> usize 函数。self 的类型是 &Self
    // x.len() 等同于 String::len(&x)
    println!("length of String {}", x.len());

    // 调用 fn push(&mut self, ch: char) 函数。self 的类型是 &mut Self,因此它有权对字符串做修改
    // x.push('!') 等同于 String::push(&mut x, '!')
    x.push('!');

    println!("length of String {}", x.len());

    // 调用 fn into_bytes(self) -> Vec<u8> 函数。注意 self 的类型,此处发生了所有权转移
    // x.into_bytes() 等同于 String::into_bytes(x)
    let v = x.into_bytes();

    // 再次调用 len(),编译失败,因为此处已经超过了 x 的生命周期
    //println!("length of String {}", x.len());
}

在这个示例中,所有的函数调用都是同样的语法,比如x.len()x.push('!')x.into_bytes()等, 但它们背后对 self 参数的传递类型完全不同,因此也就出现了不同的语义。这是需要提醒大家注意的地方。 当然,如果我们使用统一的完全限定语法,那么所有的参数传递类型在调用端都是显式写出来的。

任何借用指针的存在,都会导致原来的变量被“冻结”(Frozen)。示例如下:

rust
fn main() {
    let mut x = 1_i32;
    let p = &mut x;
    x = 2;
    println!("value of pointed : {}", p);
}

编译结果为:

txt
error[E0506]: cannot assign to `x` because it is borrowed

因为p的存在,此时对x的改变被认为是非法的。至于为什么会有这样的规定,请参考下一章。

Released under the MIT License