Rust RefCell机制

Before:为什么要出一期挺莫名其妙的东西?起因还是机考,连续2次机考工程题一分没拿了(太菜了bushi),感觉对C++的各种机制很不熟悉,而Rust又是重要的一门现代编程语言。援引StanfordCS110L前言的一段话:

如果你学过 C 并接触过一些系统编程的话,应该对 C 的内存泄漏以及指针的危险有所耳闻,但 C 的底层特性以及高效仍然让它在系统级编程中无法被例如 Java 等自带垃圾收集机制的高级语言所替代。而 Rust 的目标则是希望在 C 的高效基础上,弥补其安全不足的缺点。因此 Rust 在设计之初,就有带有很多系统编程的观点。学习 Rust,也能让你之后能用 C 语言编写出更安全更优雅的系统级代码(例如操作系统等)。

Rust Learning _RefCell机制

Rust的所有权机制

Rust的所有权机制要求每个值都有唯一的所有者(通常是变量),并且在同一时间内只能有一个所有者。所有权的转移可以通过赋值、函数参数传递或返回值来实现。相当于 C++ 中的移动语义(std::move())

在一个值的所有者变量的作用域之外(例如在另外一个函数中)对该值的访问必须通过借用(相当于 C++ 中指向变量的指针)来实现。Rust 中的借用分为两种:

  • 不可变借用(Immutable Borrow):相当于 C++ 中的 const T*,允许读取但不允许修改

  • 可变借用(Mutable Borrow):相当于 C++ 中的 T*,允许读取和修改

Rust 对变量的借用有着严格的限制:

  • 在同一时间内,只能有一个可变借用,或者多个不可变借用

  • 不能同时存在可变借用和不可变借用

  • 所有借用都必须在拥有变量的生命周期内有效,对以上规则的违反会导致编译错误。

Rust 的借用机制对编译器优化非常有帮助。由于不可变借用不能与可变借用共存,**被不可变借用指向的值只需要从堆内存中获取一次,之后可以安全地存储在寄存器或栈上缓存中。*相比之下,C++ 中也进行类似的优化,但其他函数修改 const 指针指向的值是未定义行为,可能导致不安全的代码。

Rust 的编译器可以在编译时就能“静态”地检查所有权和借用关系,在运行时无需额外检查。然而,对于堆上对象,在编译期检查所有权和借用关系是非常困难的。因此,Rust 提供了 RefCell 类型来在运行时检查所有权和借用关系。它有如下方法:

  • borrow() 与 try_borrow():获取一个不可变借用,返回 Ref 类型。如果当前存在可变借用则失败。borrow() 会 panic,相当于 C++ 中的 abort,而 try_borrow() 返回一个 Result<Ref, BorrowError>,相当于 C++ 中的 std::optional<Ref>

  • borrow_mut() 与 try_borrow_mut():获取一个可变借用,返回 RefMut 类型。如果当前存在任何借用则会失败

  • 返回的 Ref 和 RefMut 包装器实现了解引用操作符,可以像使用普通引用一样使用

  • 当 Ref 和 RefMut 的生命周期结束时,会自动减少或重置借用计数

  • 当 RefCell 的生命周期结束时,若仍有借用存在,则会 panic

C++中的std::optional

编程中,我们经常会需要表示或处理一个“可能为空”的变量,可能是一个为包含任何元素的容器,可能是一个类型的指针没有指向任何有效的对象实例,再或者是一个对象没有被赋予有效的值。

C++17中的std::optional为解决这类问题提供了简单的解决方案。optional可以看作是T类型变脸与一个布尔值的打包。其中的布尔值用来表示T是否为“空”。
std::optional可以:包含一个类型为T的值或者不包含任何值(处于"空"状态)
不包含任何值显示表示为:std::nullopt

Advantage: 明确表示值可能存在或不存在;强制使用者考虑值缺失的情况;通常比使用指针或额外标志更高效

C++中的const 成员函数

在C++中,只有被声明为const的成员函数才能被一个const类对象调用。
const 成员函数不能修改类的普通成员变量
如果想修改,需加上mutable关键字,允许 const 成员函数修改内部计数器(确实这个关键字常见于计数器)

剩余代码的实现

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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
#include <iostream>
#include <optional>
#include <stdexcept>
class RefCellError : public std::runtime_error {
public:
explicit RefCellError(const std::string& message) : std::runtime_error(message) {}
virtual ~RefCellError() = default;
};// Abstract class as base class

//invalidly call an immutable borrow
class BorrowError : public RefCellError {
public:
explicit BorrowError(const std::string& message) : RefCellError(message) {}
};
//invalidly call a mutable borrow
class BorrowMutError : public RefCellError {
public:
explicit BorrowMutError(const std::string& message) : RefCellError(message) {}
};
//still has refs when destructed
class DestructionError : public RefCellError {
public:
explicit DestructionError(const std::string& message) : RefCellError(message) {}
};

template <typename T>
class RefCell {
private:
T value;
// TODO(student)
mutable size_t borrow_num;
mutable size_t borrow_mut_num;

public:
// Forward declarations
class Ref;
class RefMut;

// Constructor
explicit RefCell(const T& initial_value):value(initial_value),borrow_mut_num(0),borrow_num(0) {}
explicit RefCell(T && initial_value):value(std::move(initial_value)),borrow_mut_num(0),borrow_num(0) {}

// Disable copying and moving for simplicity
RefCell(const RefCell&) = delete;
RefCell& operator=(const RefCell&) = delete;
RefCell(RefCell&&) = delete;
RefCell& operator=(RefCell&&) = delete;

// Borrow methods
Ref borrow() const {
// TODO(student)
if(borrow_mut_num != 0) {
throw BorrowError("RuntimeError");
}
++ borrow_num;
return Ref(*this);
}

std::optional<Ref> try_borrow() const {
// TODO(student)
if(borrow_mut_num != 0) {
return std::nullopt;
}
++ borrow_num;
return Ref(*this);
}

RefMut borrow_mut() {
// TODO(student)
if(borrow_num != 0) {
throw BorrowMutError("RuntimeError");
}
if(borrow_mut_num != 0) {
throw BorrowMutError("RuntimeError");
}
++ borrow_mut_num;
return RefMut(*this);
}

std::optional<RefMut> try_borrow_mut() {
// TODO(student)
if(borrow_num != 0) {
return std::nullopt;
}
if(borrow_mut_num != 0) {
return std::nullopt;
}
++ borrow_mut_num;
return RefMut(*this);
}

// Inner classes for borrows
class Ref {
private:
// TODO(student)
const RefCell* refcell;
bool valid = true;

public:
Ref(const RefCell &c):refcell(&c),valid(true) {}

~Ref() {
if(valid ) {
-- refcell->borrow_num;
}
}

const T& operator*() const {
if(!valid || refcell->borrow_num == 0) {
throw BorrowError("RuntimeError");
}
return refcell->value;
}

const T* operator->() const {
if(!valid || refcell->borrow_num == 0) {
throw BorrowError("RuntimeError");
}
return &refcell->value;
}

// Allow copying
Ref(const Ref& other):refcell(other.refcell),valid(other.valid) {
if(valid){
++ refcell->borrow_num;
}
}
Ref& operator=(const Ref& other) {
if(this != &other) {
if(valid) {
-- refcell->borrow_num;
}
refcell = other.refcell;
valid = other.valid;
if(valid){
++ refcell->borrow_num;
}
}
return *this;
}

// Allow moving
Ref(Ref&& other) noexcept:refcell(other.refcell),valid(other.valid) {
//TODO
other.valid = false;
}

Ref& operator=(Ref&& other) {
if (this != &other) {
if (valid) {
--refcell->borrow_num;
}
refcell = other.refcell;
valid = true;
other.valid = false;
//other = nullptr;
}
return *this;
}
};

class RefMut {
private:
// TODO(student)
RefCell* refcell;
bool valid = true;

public:
RefMut(RefCell &c):refcell(&c),valid(true) {}

~RefMut() {
// TODO(student)
if(valid ) {
-- refcell->borrow_mut_num;
}
}

T& operator*() {
// TODO(student)
if(!valid || refcell->borrow_mut_num == 0) {
throw BorrowMutError("RuntimeError");
}
return refcell->value;
}

T* operator->() {
// TODO(student)
if(!valid || refcell->borrow_mut_num == 0) {
throw BorrowMutError("RuntimeError");
}
return &refcell->value;
}

// Disable copying to ensure correct borrow rules
RefMut(const RefMut&) = delete;
RefMut& operator=(const RefMut&) = delete;

// Allow moving
RefMut(RefMut&& other) noexcept:refcell(other.refcell),valid(other.valid) {
// TODO(student)
other.valid = false;
}

RefMut& operator=(RefMut&& other) {
if (this != &other) {
if (valid) {
--refcell->borrow_mut_num;
}
refcell = other.refcell;
valid = other.valid;
other.valid = false;
//other = nullptr;
}
return *this;
}
};

// Destructor
~RefCell() {
// TODO(student)
if(borrow_mut_num > 0 || borrow_num > 0) {
throw DestructionError("RuntimeError");
}
borrow_mut_num = 0;
borrow_num = 0;
}
};

Reference


Rust RefCell机制
http://example.com/2025/04/07/Rust-RefCell机制/
Author
Yihan Zhu
Posted on
April 7, 2025
Updated on
April 7, 2025
Licensed under