我是蚂蚁背大象(Apache EventMesh PMC&Committer),文章对你有帮助给项目rocketmq-rust star,关注我GitHub:mxsm,文章有不正确的地方请您斧正,创建ISSUE提交PR~谢谢! Emal:mxsm@apache.com
1. Rust引用的两条规则
- 在任何给定的时间,您可以有一个可变引用或任意数量的不可变引用。
- 引用必须始终有效。
那么如何来理解这两条规则。
2. 引用规则理解
第一条规则:在任何给定的时间,您可以有一个可变引用或任意数量的不可变引用。
这意味着在同一时刻,要么有一个可变引用,要么有多个不可变引用,但不能同时存在可变引用和不可变引用。通过下面的代码来讲解
rust
fn main() {
let mut data = 42;
// 不可变引用
let reference1 = &data;
println!("Reference 1: {}", reference1);
// 不可变引用
let reference2 = &data;
println!("Reference 2: {}", reference2);
// 错误示例:同时存在可变引用和不可变引用
// let mutable_reference = &mut data; // 这一行将导致编译错误
// 正确示例:在不可变引用作用域结束后获取可变引用
let mutable_reference = &mut data;
*mutable_reference += 1;
println!("Mutable Reference: {}", mutable_reference);
}
然后再来看一下我在项目中遇到的相同的问题:
看一下编译错误:
调整后的
这里就违反了第一条规则。导致的编译错误
具体代码可以查看 rocketmq-rust 项目
第二条规则:引用必须始终有效。
这意味着引用不能越过其有效范围。在 Rust 中,这是通过生命周期来保证的。
rust
fn main() {
let reference;
{
let data = 42;
reference = &data; // 错误示例:data 的生命周期结束后,reference 将指向无效的数据
}
// 此处 reference 指向无效的数据,这将导致编译错误
// println!("Reference: {}", reference);
// 正确示例:使用引用的生命周期确保引用在有效范围内
let data = 42;
let reference = &data;
println!("Reference: {}", reference);
}
这第二条也就是说不能有悬垂引用。
3. 借用规则理解
首先看一下代码(代码来源:rocketmq-rust 项目):
rust
fn clean_topic_by_un_register_requests(
&mut self,
removed_broker: HashSet<String>,
reduced_broker: HashSet<String>,
) {
let mut delete_topic = HashSet::new();
while let Some((topic, queue_data_map)) = self.topic_queue_table.iter_mut().next() {
for broker_name in &removed_broker {
if let Some(removed_qd) = queue_data_map.remove(broker_name) {
println!(
"removeTopicByBrokerName, remove one broker's topic {} {:?}",
topic, removed_qd
);
}
}
if queue_data_map.is_empty() {
println!(
"removeTopicByBrokerName, remove the topic all queue {}",
topic
);
delete_topic.insert(topic);
}
for broker_name in &reduced_broker {
if let Some(queue_data) = queue_data_map.get_mut(broker_name) {
if self
.broker_addr_table
.get(broker_name)
.map_or(false, |b| b.enable_acting_master())
{
// Master has been unregistered, wipe the write perm
if self.is_no_master_exists(broker_name.as_str()) {
// Update the queue data's permission, assuming PermName is an enum
// For simplicity, I'm using 0 as PERM_WRITE value, please replace it
// with the actual value
queue_data.perm = queue_data.perm & (!PermName::PERM_WRITE as u32);
}
}
}
}
}
for topic in delete_topic {
self.topic_queue_table.remove(topic);
}
}
编译错误:
shell
error\[E0499]: cannot borrow `self.topic_queue_table` as mutable more than once at a time
\--> rocketmq-namesrv\src\route\route\_info\_manager.rs:1010:51
|
1010 | while let Some((topic, queue\_data\_map)) = self.topic\_queue\_table.iter\_mut().next() {
\| ^^^^^^^^^^^^^^^^^^^^^^ `self.topic_queue_table` was mutably borrowed here in the previous iteration of the loop
...
1046 | for topic in delete\_topic {
\| ------------ first borrow used here, in later iteration of loop
错误原因分析: Rust的 borrow checker 保证了在同一时间内要么是可变引用,要么是不可变引用。在你的代码中,self.topic_queue_table.iter_mut().next()
返回一个对 self.topic_queue_table
的可变引用,而在迭代器的生命周期内你尝试调用 self.is_no_master_exists(broker_name.as_str())
使用了 self
的不可变引用。这是不允许的,因为它违反了 borrow checker 的规则。
解决这个问题的一种方式是通过使用 clone 来避免在迭代期间对同一数据的多次可变引用。这样,你就可以在迭代器和 is_no_master_exists
函数中都使用 self
的不可变引用。
Rust的借用规则是由其借用检查器(Borrow Checker)来执行的,目的是在编译时防止数据竞争、空指针引用以及一些其他内存安全问题。以下是Rust的借用规则的主要要点:
-
不可变引用(Immutable Borrowing):
- 在任何给定时间内,可以有多个不可变引用指向同一块内存。
- 不可变引用的生命周期不能长于持有数据的可变引用的生命周期。
-
可变引用(Mutable Borrowing):
- 在给定时间内,只能有一个可变引用指向同一块内存。
- 可变引用的生命周期不能长于持有数据的不可变引用的生命周期。
- 可变引用的生命周期不能长于持有数据的其他可变引用的生命周期。
-
借用检查器:
- 借用检查器在编译时静态地检查代码,确保没有数据竞争和悬垂引用。
- 数据竞争是指多个引用同时访问同一块内存,并且至少有一个引用是可变的。
- 悬垂引用是指引用超过了其指向的数据的生命周期。
-
生命周期参数:
- 生命周期参数(Lifetime)用于指定引用的生命周期。
- 生命周期参数允许编译器验证引用的有效性,确保引用在其指向的数据仍然有效时使用。
-
借用规则的主旨:
- 在任何给定时间,要么只有一个可变引用,要么只有多个不可变引用。
- 引用的生命周期必须正确管理,以防止悬垂引用。
在 Rust 中,当使用 iter_mut()
对一个集合进行可变迭代时,next()
方法返回一个可变引用的 Option
,表示下一个元素。在你的代码中,你使用了 while let Some((topic, queue_data_map)) = self.topic_queue_table.iter_mut().next()
循环,但是你并没有在循环体内对 self.topic_queue_table
进行修改。
这样的循环可能导致 iter_mut()
一直返回同一个可变引用,因为没有对集合进行修改。在这种情况下,循环会一直返回 Some
,进入死循环。
要避免这个问题,确保在循环体内对 self.topic_queue_table
进行一些修改,以使得每次迭代都能得到不同的元素。例如,你可以使用 drain
方法或者使用索引来修改集合。下面是一个例子,使用 drain
方法:
javascript
while let Some((topic, queue_data_map)) = self.topic_queue_table.iter_mut().next() {
// 在这里进行集合的修改,例如使用 drain 方法
let _ = self.topic_queue_table.drain(topic);
// 或者使用索引
// let _ = self.topic_queue_table.remove(topic);
}
这里的 drain
方法会将集合中的元素移除,确保下一次迭代时能够得到不同的元素。当然,具体的修改方式取决于你的业务逻辑和数据结构的设计。