内部可变性
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
中的值是非常容易的。
而且还有很多其他的方法,比如replace_with
使用的是闭包。
但是你要小心使用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!")
};
}