内部可变性

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!") }; }