内部可变性
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_issued
和screen_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
做完后还是有锁。我们该如何阻止它呢?Mutex
在MutexGuard
超出范围时就会被解锁。"超出范围"表示该代码块已经完成。比如说:
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 let
或match
比较好。
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!") }; }