以下内容为本人学习过程中的记录笔记,其中可能存在不准确或错误,欢迎勘误及指正
借用检查规则
在开始说明内部可变性前,我们先回顾一下Rust的借用检查规则
规则一
在特定作用域内,对某一块数据只能有一个可变引用,以避免产生数据竞争
规则二
对一个数据可以同时存在多个不可变引用,但不能同时存在可变引用与不可变引用
规则三
被引用的数据需要一直保持有效,即借用的生命周期不能超过被借用对象的生命周期
内部可变性
Rust 的借用检查规则虽能确保程序运行安全,但在实际编写中,却欠缺了一些灵活性。例如,当我们使用完一个数据的不可变引用后,即便明知更改原数据的操作是安全的,受借用检查规则的限制,也无法实现。如下述代码所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| fn main() { let mut data = vec![1, 2, 3];
let immutable_ref = &data;
let mutable_ref = &mut data;
mutable_ref.push(4); println!("Mutable reference: {:?}", mutable_ref);
println!("Immutable reference: {:?}", immutable_ref); }
|
从执行逻辑看,上面的代码其实是安全的。然而,由于借用检查规则具有强制性,导致这段代码最终无法通过编译。此时,我们就可以使用内部可变性的方法将编译时检查延长至运行时检查,比如下面的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| use std::cell::RefCell;
fn main() { let data = RefCell::new(vec![1, 2, 3]);
let immutable_ref = &data;
{ let mut mutable_borrow = immutable_ref.borrow_mut(); mutable_borrow.push(4); println!("Mutable borrow: {:?}", mutable_borrow); }
let immutable_borrow = immutable_ref.borrow(); println!("Immutable borrow: {:?}", immutable_borrow); }
|
borrow方法
borrow
方法用于获取RefCell
内部值的不可变引用&T
。这意味着可以通过这个引用读取内部的值,但不能对其进行修改
1 2 3 4 5 6 7 8 9 10 11 12
| use std::cell::RefCell;
fn main() { let cell = RefCell::new(5); { let borrowed = cell.borrow(); println!("The value is: {}", borrowed); } }
|
borrow_mut方法
borrow_mut
方法用于获取RefCell
内部值的可变引用&mut T
。借助这个可变引用,不仅可以读取内部的值,还能对其进行修改
1 2 3 4 5 6 7 8 9 10 11
| use std::cell::RefCell;
fn main() { let cell = RefCell::new(5); { let mut borrowed_mut = cell.borrow_mut(); *borrowed_mut = 6; println!("The updated value is: {}", borrowed_mut); } }
|
需要注意的是,borrow
和borrow_mut
方法同样会在运行时检查借用规则,使用时需要确保同一时间没有其他借用(无论是可变借用还是不可变借用)存在,否则会触发panic ,比如下面的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| use std::cell::RefCell;
fn main() { let cell = RefCell::new(10);
let mut mut_borrow = cell.borrow_mut(); *mut_borrow = 20;
let immut_borrow = cell.borrow(); println!("Immutable borrow value: {}", immut_borrow);
*mut_borrow = 30; println!("Updated mutable borrow value: {}", mut_borrow); }
|
复杂示例
如果理解了内部可变性的具体作用,那我们继续尝试一下更复杂的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
| pub trait Messenger { fn send(&self, msg: &str); }
pub struct LimitTracker<'a, T: Messenger> { messenger: &'a T, value: usize, max: usize, }
impl<'a, T> LimitTracker<'a, T>
where T: Messenger, { pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> { LimitTracker { messenger, value: 0, max, } }
pub fn set_value(&mut self, value: usize) { self.value = value;
let percentage_of_max = self.value as f64 / self.max as f64;
if percentage_of_max >= 1.0 { self.messenger.send("Error: You are over your quota!"); } else if percentage_of_max >= 0.9 { self.messenger .send("Urgent warning: You've used up over 90% of your quota!"); } else if percentage_of_max >= 0.75 { self.messenger .send("Warning: You've used up over 75% of your quota!"); } } }
#[cfg(test)] mod tests { use super::*;
struct MockMessenger { sent_messages: Vec<String>, }
impl MockMessenger { fn new() -> MockMessenger { MockMessenger { sent_messages: vec![], } } }
impl Messenger for MockMessenger { fn send(&self, message: &str) { self.sent_messages.push(String::from(message)); } }
#[test] fn it_sends_an_over_75_percent_warning_message() { let mock_messenger = MockMessenger::new(); let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);
limit_tracker.set_value(80);
assert_eq!(mock_messenger.sent_messages.len(), 1); } }
|
在这段代码中,MockMessenger
结构体的send
方法最初的实现存在问题,因为send
方法接收的是&self
(不可变引用),而方法中尝试修改sent_messages
字段self.sent_messages.push(String::from(message))
,这违反了Rust的借用检查规则,即不可变引用不能用于修改数据,此时我们可以使用内部可变性来实现想要的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
| pub trait Messenger { fn send(&self, msg: &str); }
pub struct LimitTracker<'a, T: Messenger> { messenger: &'a T, value: usize, max: usize, }
impl<'a, T> LimitTracker<'a, T>
where T: Messenger, { pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> { LimitTracker { messenger, value: 0, max, } }
pub fn set_value(&mut self, value: usize) { self.value = value;
let percentage_of_max = self.value as f64 / self.max as f64;
if percentage_of_max >= 1.0 { self.messenger.send("Error: You are over your quota!"); } else if percentage_of_max >= 0.9 { self.messenger .send("Urgent warning: You've used up over 90% of your quota!"); } else if percentage_of_max >= 0.75 { self.messenger .send("Warning: You've used up over 75% of your quota!"); } } }
#[cfg(test)] mod tests { use super::*; use std::cell::RefCell;
struct MockMessenger { sent_messages: RefCell<Vec<String>>, }
impl MockMessenger { fn new() -> MockMessenger { MockMessenger { sent_messages: RefCell::new(vec![]), } } }
impl Messenger for MockMessenger { fn send(&self, message: &str) { self.sent_messages.borrow_mut().push(String::from(message)); } }
#[test] fn it_sends_an_over_75_percent_warning_message() { let mock_messenger = MockMessenger::new(); let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);
limit_tracker.set_value(80);
assert_eq!(mock_messenger.sent_messages.borrow().len(), 1); } }
|