以下内容为本人学习过程中的记录笔记,其中可能存在不准确或错误,欢迎勘误及指正

借用检查规则

在开始说明内部可变性前,我们先回顾一下Rust的借用检查规则

规则一

在特定作用域内,对某一块数据只能有一个可变引用,以避免产生数据竞争

规则二

对一个数据可以同时存在多个不可变引用,但不能同时存在可变引用与不可变引用

规则三

被引用的数据需要一直保持有效,即借用的生命周期不能超过被借用对象的生命周期

内部可变性

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

从执行逻辑看,上面的代码其实是安全的。然而,由于借用检查规则具有强制性,导致这段代码最终无法通过编译。此时,我们就可以使用内部可变性的方法将编译时检查延长至运行时检查,比如下面的代码

rust
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]); // 注意,这里的'data'是不可变的

// 创建一个不可变引用
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。这意味着可以通过这个引用读取内部的值,但不能对其进行修改

rust
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);
// borrowed 是不可变引用,不能进行修改操作,例如下面这行代码会编译错误
// *borrowed = 6;
}
// 当 borrowed 离开作用域后,不可变借用失效
}

borrow_mut方法

borrow_mut方法用于获取RefCell内部值的可变引用&mut T。借助这个可变引用,不仅可以读取内部的值,还能对其进行修改

rust
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);
}
// 当 borrowed_mut 离开作用域后,可变借用失效
}

需要注意的是,borrowborrow_mut方法同样会在运行时检查借用规则,使用时需要确保同一时间没有其他借用(无论是可变借用还是不可变借用)存在,否则会触发panic ,比如下面的代码

rust
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;

// 尝试获取不可变借用,此时会导致程序 panic
// 因为当前已经存在一个可变借用 mut_borrow
let immut_borrow = cell.borrow();
println!("Immutable borrow value: {}", immut_borrow);

// 这里的代码不会被执行,因为在获取不可变借用时程序已经 panic
*mut_borrow = 30;
println!("Updated mutable borrow value: {}", mut_borrow);
}

复杂示例

如果理解了内部可变性的具体作用,那我们继续尝试一下更复杂的代码

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
pub trait Messenger {
// send 方法接收一个不可变的 self 引用和一个字符串切片作为消息内容
fn send(&self, msg: &str);
}

// 定义一个公开的泛型结构体 LimitTracker,用于跟踪某个值与最大值的比例
pub struct LimitTracker<'a, T: Messenger> {
messenger: &'a T,
// value 存储当前跟踪的值
value: usize,
// max 存储允许的最大值
max: usize,
}

impl<'a, T> LimitTracker<'a, T>
// 泛型类型参数 T 必须实现 Messenger trait
where
T: Messenger,
{
// 接收一个对实现了 Messenger trait 的类型 T 的不可变引用和一个最大值
// 返回一个新的 LimitTracker 实例,初始的 value 为 0
pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {
LimitTracker {
messenger,
value: 0,
max,
}
}

// 实例方法 set_value,用于设置当前跟踪的值
pub fn set_value(&mut self, value: usize) {
// 将传入的值赋给 self.value
self.value = value;

// 计算当前值占最大值的比例,转换为 f64 类型进行计算
let percentage_of_max = self.value as f64 / self.max as f64;

// 如果比例大于等于 1.0,表示超过了配额
if percentage_of_max >= 1.0 {
// 调用 messenger 的 send 方法发送错误消息
self.messenger.send("Error: You are over your quota!");
// 如果比例大于等于 0.9,表示使用量超过了 90%
} else if percentage_of_max >= 0.9 {
// 调用 messenger 的 send 方法发送紧急警告消息
self.messenger
.send("Urgent warning: You've used up over 90% of your quota!");
// 如果比例大于等于 0.75,表示使用量超过了 75%
} else if percentage_of_max >= 0.75 {
// 调用 messenger 的 send 方法发送警告消息
self.messenger
.send("Warning: You've used up over 75% of your quota!");
}
}
}

#[cfg(test)]
mod tests {
use super::*;

// 定义一个结构体 MockMessenger,用于在测试中模拟 Messenger
struct MockMessenger {
// sent_messages 存储发送过的消息
sent_messages: Vec<String>,
}

impl MockMessenger {
// 关联函数 new,用于创建 MockMessenger 结构体的实例
// 返回一个新的 MockMessenger 实例,初始的 sent_messages 为空向量
fn new() -> MockMessenger {
MockMessenger {
sent_messages: vec![],
}
}
}

// 为 MockMessenger 结构体实现 Messenger trait
impl Messenger for MockMessenger {
// 实现 send 方法,将接收到的消息添加到 sent_messages 向量中
fn send(&self, message: &str) {
// 注意:这里代码存在错误,因为 self 是不可变引用,不能修改 sent_messages
self.sent_messages.push(String::from(message));
}
}

// 测试函数,用于测试当使用量超过 75% 时是否发送了警告消息
#[test]
fn it_sends_an_over_75_percent_warning_message() {
// 创建一个 MockMessenger 实例
let mock_messenger = MockMessenger::new();
// 创建一个 LimitTracker 实例,使用 MockMessenger 作为消息发送器,最大值为 100
let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);

// 设置当前值为 80,超过了 75% 的阈值
limit_tracker.set_value(80);

// 断言 MockMessenger 发送的消息数量为 1
assert_eq!(mock_messenger.sent_messages.len(), 1);
}
}

在这段代码中,MockMessenger结构体的send方法最初的实现存在问题,因为send方法接收的是&self(不可变引用),而方法中尝试修改sent_messages字段self.sent_messages.push(String::from(message)),这违反了Rust的借用检查规则,即不可变引用不能用于修改数据,此时我们可以使用内部可变性来实现想要的功能

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 {
// send 方法接收一个不可变的 self 引用和一个字符串切片作为消息内容
fn send(&self, msg: &str);
}

// 定义一个公开的泛型结构体 LimitTracker,用于跟踪某个值与最大值的比例
pub struct LimitTracker<'a, T: Messenger> {
messenger: &'a T,
// value 存储当前跟踪的值
value: usize,
// max 存储允许的最大值
max: usize,
}

impl<'a, T> LimitTracker<'a, T>
// 泛型类型参数 T 必须实现 Messenger trait
where
T: Messenger,
{
// 接收一个对实现了 Messenger trait 的类型 T 的不可变引用和一个最大值
// 返回一个新的 LimitTracker 实例,初始的 value 为 0
pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {
LimitTracker {
messenger,
value: 0,
max,
}
}

// 实例方法 set_value,用于设置当前跟踪的值
pub fn set_value(&mut self, value: usize) {
// 将传入的值赋给 self.value
self.value = value;

// 计算当前值占最大值的比例,转换为 f64 类型进行计算
let percentage_of_max = self.value as f64 / self.max as f64;

// 如果比例大于等于 1.0,表示超过了配额
if percentage_of_max >= 1.0 {
// 调用 messenger 的 send 方法发送错误消息
self.messenger.send("Error: You are over your quota!");
// 如果比例大于等于 0.9,表示使用量超过了 90%
} else if percentage_of_max >= 0.9 {
// 调用 messenger 的 send 方法发送紧急警告消息
self.messenger
.send("Urgent warning: You've used up over 90% of your quota!");
// 如果比例大于等于 0.75,表示使用量超过了 75%
} else if percentage_of_max >= 0.75 {
// 调用 messenger 的 send 方法发送警告消息
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 类型,内部存储一个 String 类型的向量
// 使用 RefCell 是为了在不可变引用的情况下修改 sent_messages 向量
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) {
// 通过 borrow_mut 方法获取 sent_messages 的可变借用
// 这样就可以在不可变的 MockMessenger 实例上修改 sent_messages 向量
// 将传入的消息转换为 String 类型并添加到 sent_messages 向量中
self.sent_messages.borrow_mut().push(String::from(message));
}
}

#[test]
fn it_sends_an_over_75_percent_warning_message() {
// 创建一个 MockMessenger 实例,用于模拟消息发送器
let mock_messenger = MockMessenger::new();
// 创建一个 LimitTracker 实例,传入 MockMessenger 的引用和最大值 100
// 这里使用可变的 limit_tracker 以便后续调用 set_value 方法
let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);

// 调用 LimitTracker 的 set_value 方法,将当前值设置为 80
// 由于 80 超过了最大值 100 的 75%,应该会触发消息发送
limit_tracker.set_value(80);

// 通过 borrow 方法获取 sent_messages 的不可变借用
// 断言 sent_messages 向量的长度为 1,即验证是否发送了一条消息
assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);
}
}