内部可变性

Cell

内部可变性的意思是在内部有一点可变性。还记得在Rust中,你需要用mut来改变一个变量吗?也有一些方法可以不用mut这个词来改变它们。这是因为Rust有一些方法可以让你安全地在一个不可变的结构里面改变值。每一种方法都遵循一些规则,确保改变值仍然是安全的。

首先,让我们看一个简单的例子,我们会想要这样做:想象一下,一个叫PhoneModel的结构体有很多字段:

struct PhoneModel {
    company_name: String,
    model_name: String,
    screen_size: f32,
    memory: usize,
    date_issued: u32,
    on_sale: bool,
}

fn main() {
    let super_phone_3000 = PhoneModel {
        company_name: "YY Electronics".to_string(),
        model_name: "Super Phone 3000".to_string(),
        screen_size: 7.5,
        memory: 4_000_000,
        date_issued: 2020,
        on_sale: true,
    };

}

PhoneModel中的字段最好是不可变的,因为我们不希望数据改变。比如说date_issuedscreen_size永远不会变。

但是里面有一个字段叫on_sale。一个手机型号先是会有销售(true),但是后来公司会停止销售。我们能不能只让这一个字段可变?因为我们不想写let mut super_phone_3000。如果我们这样做,那么每个字段都会变得可变。

Rust有很多方法可以让一些不可变的东西里面有一些安全的可变性,最简单的方法叫做Cell。首先我们使用use std::cell::Cell,这样我们就可以每次只写Cell而不是std::cell::Cell

然后我们把on_sale: bool改成on_sale: Cell<bool>。现在它不是一个bool:它是一个Cell,容纳了一个bool

Cell有一个叫做.set()的方法,在这里你可以改变值。我们用.set()on_sale: true改为on_sale: Cell::new(true)

use std::cell::Cell;

struct PhoneModel {
    company_name: String,
    model_name: String,
    screen_size: f32,
    memory: usize,
    date_issued: u32,
    on_sale: Cell<bool>,
}

fn main() {
    let super_phone_3000 = PhoneModel {
        company_name: "YY Electronics".to_string(),
        model_name: "Super Phone 3000".to_string(),
        screen_size: 7.5,
        memory: 4_000_000,
        date_issued: 2020,
        on_sale: Cell::new(true),
    };

    // 10 years later, super_phone_3000 is not on sale anymore
    super_phone_3000.on_sale.set(false);
}

Cell 适用于所有类型,但对简单的 Copy 类型效果最好,因为它给出的是值,而不是引用。Cell有一个叫做get()的方法,它只对Copy类型有效。

另一个可以使用的类型是 RefCell

RefCell

RefCell是另一种无需声明mut而改变值的方法。它的意思是 "引用单元格",就像 Cell,但使用引用而不是副本。

我们将创建一个 User 结构。到目前为止,你可以看到它与 Cell 类似。

use std::cell::RefCell;

#[derive(Debug)]
struct User {
    id: u32,
    year_registered: u32,
    username: String,
    active: RefCell<bool>,
    // Many other fields
}

fn main() {
    let user_1 = User {
        id: 1,
        year_registered: 2020,
        username: "User 1".to_string(),
        active: RefCell::new(true),
    };

    println!("{:?}", user_1.active);
}

这样就可以打印出RefCell { value: true }

RefCell的方法有很多。其中两种是.borrow().borrow_mut()。使用这些方法,你可以做与&&mut相同的事情。规则都是一样的:

  • 多个不可变借用可以
  • 一个可变的借用可以
  • 但可变和不可变借用在一起是不行的

所以改变RefCell中的值是非常容易的。


#![allow(unused)]
fn main() {
// 🚧
user_1.active.replace(false);
println!("{:?}", user_1.active);
}

而且还有很多其他的方法,比如replace_with使用的是闭包。


#![allow(unused)]
fn main() {
// 🚧
let date = 2020;

user_1
    .active
    .replace_with(|_| if date < 2000 { true } else { false });
println!("{:?}", user_1.active);
}

但是你要小心使用RefCell,因为它是在运行时而不是编译时检查借用。运行时是指程序实际运行的时候(编译后)。所以这将会被编译,即使它是错误的。

use std::cell::RefCell;

#[derive(Debug)]
struct User {
    id: u32,
    year_registered: u32,
    username: String,
    active: RefCell<bool>,
    // Many other fields
}

fn main() {
    let user_1 = User {
        id: 1,
        year_registered: 2020,
        username: "User 1".to_string(),
        active: RefCell::new(true),
    };

    let borrow_one = user_1.active.borrow_mut(); // first mutable borrow - okay
    let borrow_two = user_1.active.borrow_mut(); // second mutable borrow - not okay
}

但如果你运行它,它就会立即崩溃。

thread 'main' panicked at 'already borrowed: BorrowMutError', C:\Users\mithr\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib/rustlib/src/rust\src\libcore\cell.rs:877:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `target\debug\rust_book.exe` (exit code: 101)

already borrowed: BorrowMutError是重要的部分。所以当你使用RefCell时,好编译运行检查。

Mutex

Mutex是另一种改变数值的方法,不需要声明mut。Mutex的意思是mutual exclusion,也就是 "一次只能改一个"。这就是为什么Mutex是安全的,因为它每次只让一个进程改变它。为了做到这一点,它使用了.lock()Lock就像从里面锁上一扇门。你进入一个房间,锁上门,现在你可以在房间里面改变东西。别人不能进来阻止你,因为你把门锁上了。

Mutex通过例子更容易理解:

use std::sync::Mutex;

fn main() {
    let my_mutex = Mutex::new(5); // A new Mutex<i32>. We don't need to say mut
    let mut mutex_changer = my_mutex.lock().unwrap(); // mutex_changer is a MutexGuard
                                                     // It has to be mut because we will change it
                                                     // Now it has access to the Mutex
                                                     // Let's print my_mutex to see:

    println!("{:?}", my_mutex); // This prints "Mutex { data: <locked> }"
                                // So we can't access the data with my_mutex now,
                                // only with mutex_changer

    println!("{:?}", mutex_changer); // This prints 5. Let's change it to 6.

    *mutex_changer = 6; // mutex_changer is a MutexGuard<i32> so we use * to change the i32

    println!("{:?}", mutex_changer); // Now it says 6
}

但是mutex_changer做完后还是有锁。我们该如何阻止它呢?MutexMutexGuard超出范围时就会被解锁。"超出范围"表示该代码块已经完成。比如说:

use std::sync::Mutex;

fn main() {
    let my_mutex = Mutex::new(5);
    {
        let mut mutex_changer = my_mutex.lock().unwrap();
        *mutex_changer = 6;
    } // mutex_changer goes out of scope - now it is gone. It is not locked anymore

    println!("{:?}", my_mutex); // Now it says: Mutex { data: 6 }
}

如果你不想使用不同的{}代码块,你可以使用std::mem::drop(mutex_changer)std::mem::drop的意思是 "让这个超出范围"。

use std::sync::Mutex;

fn main() {
    let my_mutex = Mutex::new(5);
    let mut mutex_changer = my_mutex.lock().unwrap();
    *mutex_changer = 6;
    std::mem::drop(mutex_changer); // drop mutex_changer - it is gone now
                                   // and my_mutex is unlocked

    println!("{:?}", my_mutex); // Now it says: Mutex { data: 6 }
}

你必须小心使用 Mutex,因为如果另一个变量试图 lock它,它会等待。

use std::sync::Mutex;

fn main() {
    let my_mutex = Mutex::new(5);
    let mut mutex_changer = my_mutex.lock().unwrap(); // mutex_changer has the lock
    let mut other_mutex_changer = my_mutex.lock().unwrap(); // other_mutex_changer wants the lock
                                                            // the program is waiting
                                                            // and waiting
                                                            // and will wait forever.

    println!("This will never print...");
}

还有一种方法是try_lock()。然后它会试一次,如果没能锁上就会放弃。try_lock().unwrap()就不要做了,因为如果不成功它就会崩溃。if letmatch比较好。

use std::sync::Mutex;

fn main() {
    let my_mutex = Mutex::new(5);
    let mut mutex_changer = my_mutex.lock().unwrap();
    let mut other_mutex_changer = my_mutex.try_lock(); // try to get the lock

    if let Ok(value) = other_mutex_changer {
        println!("The MutexGuard has: {}", value)
    } else {
        println!("Didn't get the lock")
    }
}

另外,你不需要创建一个变量来改变Mutex。你可以直接这样做:

use std::sync::Mutex;

fn main() {
    let my_mutex = Mutex::new(5);

    *my_mutex.lock().unwrap() = 6;

    println!("{:?}", my_mutex);
}

*my_mutex.lock().unwrap() = 6;的意思是 "解锁my_mutex并使其成为6"。没有任何变量来保存它,所以你不需要调用 std::mem::drop。如果你愿意,你可以做100次--这并不重要。

use std::sync::Mutex;

fn main() {
    let my_mutex = Mutex::new(5);

    for _ in 0..100 {
        *my_mutex.lock().unwrap() += 1; // locks and unlocks 100 times
    }

    println!("{:?}", my_mutex);
}

RwLock

RwLock的意思是 "读写锁"。它像Mutex,但也像RefCell。你用.write().unwrap()代替.lock().unwrap()来改变它。但你也可以用.read().unwrap()来获得读权限。它和RefCell一样,遵循这些规则:

  • 很多.read()变量可以
  • 一个.write()变量可以
  • 但多个.write().read().write()一起是不行的

如果在无法访问的情况下尝试.write(),程序将永远运行。

use std::sync::RwLock;

fn main() {
    let my_rwlock = RwLock::new(5);

    let read1 = my_rwlock.read().unwrap(); // one .read() is fine
    let read2 = my_rwlock.read().unwrap(); // two .read()s is also fine

    println!("{:?}, {:?}", read1, read2);

    let write1 = my_rwlock.write().unwrap(); // uh oh, now the program will wait forever
}

所以我们用std::mem::drop,就像用Mutex一样。

use std::sync::RwLock;
use std::mem::drop; // We will use drop() many times

fn main() {
    let my_rwlock = RwLock::new(5);

    let read1 = my_rwlock.read().unwrap();
    let read2 = my_rwlock.read().unwrap();

    println!("{:?}, {:?}", read1, read2);

    drop(read1);
    drop(read2); // we dropped both, so we can use .write() now

    let mut write1 = my_rwlock.write().unwrap();
    *write1 = 6;
    drop(write1);
    println!("{:?}", my_rwlock);
}

而且你也可以使用try_read()try_write()

use std::sync::RwLock;

fn main() {
    let my_rwlock = RwLock::new(5);

    let read1 = my_rwlock.read().unwrap();
    let read2 = my_rwlock.read().unwrap();

    if let Ok(mut number) = my_rwlock.try_write() {
        *number += 10;
        println!("Now the number is {}", number);
    } else {
        println!("Couldn't get write access, sorry!")
    };
}