內部可變性
Cell
內部可變性(Interior mutability) 的意思是在內部有一點可變性。還記得在 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
。一個手機型號會先是銷售中 (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:它是個容納了 bool
的 Cell
。
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 年後, super_phone_3000 不再銷售了 super_phone_3000.on_sale.set(false); }
Cell
適用於所有型別,但對簡單的 Copy 型別效果最好,因為它給出的是值,而不是參考。Cell
有個叫做 get()
的方法,它只對 Copy 型別有效。
另一個你可以使用的型別是 RefCell
。
RefCell
RefCell
是另一種無需宣告 mut
而改變值的方式。它的意思是 "reference cell",就像 Cell
,但使用的是參考而不是拷貝 (copy)。
我們將建立 User
結構。到目前為止,你可以看到它與 Cell
類似:
use std::cell::RefCell; #[derive(Debug)] struct User { id: u32, year_registered: u32, username: String, active: RefCell<bool>, // 許多其它欄位 } 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>, // 許多其它欄位 } 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(); // 第一個可變借用 - okay let borrow_two = user_1.active.borrow_mut(); // 第二個可變借用 - 不 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
就能改變數值的方式。互斥鎖的意思是 mutual exclusion
,也就是"一次只能改一個"。這就是為什麼 Mutex
是安全的,因為它每次只讓一個執行緒改變它。為了做到這一點,它使用了 .lock()
。Lock
就像從裡面鎖上門。你進入房間,鎖上門,現在你可以在房間裡面改變東西。別人不能進來阻止你,因為你把門鎖上了。
透過範例更容易理解 Mutex
:
use std::sync::Mutex; fn main() { let my_mutex = Mutex::new(5); // 新的 Mutex<i32>. 我們不需要加 mut let mut mutex_changer = my_mutex.lock().unwrap(); // mutex_changer 是個 MutexGuard // 它必須是 mut 因為我們將會改變它 // 現在它能存取 Mutex 了 // 讓我們印 my_mutex 來看: println!("{:?}", my_mutex); // 印出 "Mutex { data: <locked> }" // 因此我們現在不能用 my_mutex 存取資料, // 只能用 mutex_changer println!("{:?}", mutex_changer); // 印出 5. 讓我們改成 6. *mutex_changer = 6; // mutex_changer 是個 MutexGuard<i32> 所以我們用 * 來改變 i32 println!("{:?}", mutex_changer); // 現在它說是 6 }
但是 mutex_changer
做完後還是持有著鎖。我們該如何停止呢?Mutex
在 MutexGuard
超出範圍 (out of scope) 時就會被解鎖。"超出範圍"表示該程式碼區塊已經結束執行。比如說:
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 已經超出範圍 - 現在它不見了. 不再鎖著了 println!("{:?}", my_mutex); // 現在它會說: 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); // 丟棄 mutex_changer ── 現在不見了 // 而且 my_mutex 解鎖了 println!("{:?}", my_mutex); // 現在它會說: 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 拿到鎖 let mut other_mutex_changer = my_mutex.lock().unwrap(); // other_mutex_changer 想拿鎖 // 程式正在等 // 還在等 // 又會等到永遠. 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(); // 試著拿到鎖 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; // 上鎖又解鎖 100 次 } 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(); // 一個 .read() 很好 let read2 = my_rwlock.read().unwrap(); // 二個 .read() 也很好 println!("{:?}, {:?}", read1, read2); let write1 = my_rwlock.write().unwrap(); // 噢哦, 現在程式會永遠等待 }
所以我們用 std::mem::drop
,就像用 Mutex
一樣。
use std::sync::RwLock; use std::mem::drop; // 我們將會使用 drop() 許多次 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); // 一起丟棄, 那現在我們才能使用 .write() 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!") }; }