Wang's blog

二、所有权与借用

Published on

RAII(资源获取即初始化)

为了能够正常释放资源,避免出现C/C++中很容易出现的资源泄漏,Rust强制执行RAII。RAII要求任何对象在离开其作用域时,必须调用其析构函数并释放资源。Rust中的析构函数通过Drop特性实现。

fn create_box() {
    // 在函数中申请堆上资源
    let _box1 = Box::new(3i32);

    // 函数结束时释放_box1
}

fn main() {
    // 在主函数中申请堆上资源
    let _box2 = Box::new(5i32);

    {
        // 在一个代码块中申请堆上资源
        let _box3 = Box::new(4i32);

        // 代码块结束时释放_box3
    }

    // 申请很多资源,无需手动释放
    for _ in 0u32..1_000 {
        create_box();
    }

    // 主函数结束时释放_box2
}

所有权与移动语义

所有权:为了能够安全地释放资源,Rust引入了所有权规则:

  • 每个变量有且只能有一个所有者
  • 在退出作用域时,所有者负责释放资源

这避免了多次释放同一资源,另外也有一些变量不拥有资源(即引用)。

移动语义:默认情况下,在使用let进行赋值,或者向函数传递参数时,所有权会进行转移。在进行移动后,原来的变量不能再使用,这避免了悬挂指针。

// 此函数获取堆上资源的所有权
fn destroy_box(c: Box<i32>) {
    println!("Destroying a box that contains {}", c);

    // 函数结束时c被释放
}

// 栈上资源只会进行复制
let x = 5u32;
let y = x;

// 堆上资源会进行移动,移动后a不能再使用
let a = Box::new(5i32);
let b = a;

// 调用函数将b的所有权移动到函数中,移动后b不能再使用
destroy_box(b);

可变性变化

变量的可变性在移动时可以进行变化。

// 声明不可变变量
let immutable_box = Box::new(5u32);

// 移动的同时修改可变性
let mut mutable_box = immutable_box;
*mutable_box = 4;

部分移动

对于一个复杂变量,可能存在只移动了它的一部分的情况。此时,该变量不能作为一个整体进行使用,但是其未移动的部分可以单独使用。

struct Person {
    name: String,
    age: Box<u8>,
}

// 定义一个结构体变量
let person = Person {
    name: String::from("Alice"),
    age: Box::new(20),
};

// 移动了name,但是age没有移动
let Person { name, ref age } = person;

// 错误,部分移动的变量不能作为整体使用
println!("The person struct is {:?}", person);

// 正确,未移动的部分可以继续使用
println!("The person's age from person struct is {}", person.age);

借用与引用

在大部分情况下,我们只需要访问资源但并不需要获取其所有权。为了达到这个目的,Rust提供了借用机制,无需传递变量本身(T),只需传递变量的引用(&T)即可。编译器保证引用总是有效的,这是通过在引用存在时,强制不能释放所引用的变量达到的。

// 此函数获取变量所有权并释放
fn eat_box_i32(boxed_i32: Box<i32>) {
    println!("Destroying box that contains {}", boxed_i32);
}

// 此函数只借用变量,不获取所有权
fn borrow_i32(borrowed_i32: &i32) {
    println!("This int is: {}", borrowed_i32);
}

fn main() {
    // 建立一个Box<i32>(堆上)与一个i32(栈上)
    let boxed_i32 = Box::new(5_i32);
    let stacked_i32 = 6_i32;

    // 借用,未移动所有权
    borrow_i32(&boxed_i32);
    borrow_i32(&stacked_i32);

    {
        // 声明一个Box内容的引用
        let _ref_to_i32: &i32 = &boxed_i32;

        // 移动并释放Box,在后续仍存在该变量的引用的情况下,不能通过编译
        eat_box_i32(boxed_i32);

        // 在释放后仍然使用变量的引用,不能通过编译
        borrow_i32(_ref_to_i32);

        // 引用在作用域结束时结束借用
    }

    // 此时可以移动并释放
    eat_box_i32(boxed_i32);
}

引用的可变性

可变变量可以通过可变引用(&mut T)的方式同时借用读写权限,也可以通过不可变引用(&T)的方式只借用读权限。

struct Book {
    author: &'static str,
    title: &'static str,
    year: u32,
}

// 此函数使用不可变借用
fn borrow_book(book: &Book) {
    println!("I immutably borrowed {} - {} edition", book.title, book.year);
}

// 此函数使用可变借用并修改变量值
fn new_edition(book: &mut Book) {
    book.year = 2014;
    println!("I mutably borrowed {} - {} edition", book.title, book.year);
}

// 建立不可变变量
let immutabook = Book {
    author: "Douglas Hofstadter",
    title: "Gödel, Escher, Bach",
    year: 1979,
};

// 复制为可变变量
let mut mutabook = immutabook;

// 不可变借用一个不可变变量
borrow_book(&immutabook);

// 不可变借用一个可变变量
borrow_book(&mutabook);

// 可变借用一个可变变量
new_edition(&mut mutabook);

// 错误,不能可变借用一个不可变变量
new_edition(&mut immutabook);

借用数量

同时可以有任意个不可变借用,或者一个可变借用,这是很容易理解的。

ref关键字

在使用match进行模式匹配或使用let进行绑定时,可以使用ref关键字获取对应字段的引用。

struct Point { x: i32, y: i32 }

// 在左侧使用ref获取引用与在右侧使用&等价
let c = 'Q';
let ref ref_c1 = c;
let ref_c2 = &c;

// 在解构结构体时使用ref获取引用
let point = Point { x: 0, y: 0 };
let Point { x: ref ref_to_x, y: _ } = point;

// 在解构结构体时使用ref mut获取可变引用
let mut mutable_point = point;
let Point { x: _, y: ref mut mut_ref_to_y } = mutable_point;

// 在解构枚举时使用ref mut获取可变引用
let mut mutable_tuple = (Box::new(5u32), 3u32);
let (_, ref mut last) = mutable_tuple;