本文上下文中的
借用
引用
是相同概念,只是按照常用习惯书写
再借(引)用
以下面的代码为例,根据借用规则我们会认为代码无法编译通过,但是事实是代码正常运行
fn main() {
let mut name = String::from("karima");
let borrow= &mut name;
let reborrow = &*borrow;
println!("{}", reborrow);
println!("{}", borrow);
}
原因在于这段代码
let reborrow = &*borrow;
对引用进行解引用,再获取不可变引用
,在上述代码不就导致了可变、不可变引用同时存在了吗?
但其实并不能这么理解,此处会发生 再借用
:reborrow 是对原始引用的 再引用(再借用)
再借用不会破坏借用规则,同时再借用也存在自己一些规则
再借用的规则
还是前面的代码
fn main() {
let mut name = String::from("karima");
let borrow= &mut name;
let reborrow = &*borrow;
println!("{}", reborrow);
println!("{}", borrow);
}
再借用是对于原始引用的引用,所以其生命周期依赖于原始引用
那么,再借用作用范围内,对于原始引用存在什么影响呢?
这就是接下来我们要讨论的较为细节的内容
对引用创建引用会有三种情况,分别是
- 对可变引用创建不可变再引用
- 对可变引用创建可变再引用
- 对不可变引用创建不可变再引用
这三种情况下对原始引用存在不同影响,分别分析
1. 对可变引用创建不可变再引用
fn main() {
let mut name = String::from("karima");
let borrow= &mut name;
let reborrow = &*borrow;
println!("{}", borrow);
// *borrow = String::from("marry"); // 此段代码将会发生编译错误
println!("{}", reborrow);
println!("{}", borrow);
}
上面的代码对可变引用 borrow
创建了 不可变再借用 reborrow
,此时在 不可变再借用
的生命周期内又使用了原始的可变引用,按照传统的认知,此时会发生冲突导致错误,但是再借用不会破坏借用规则
但是如果在 不可变再借用
生命周期内尝试使用原始的可变引用去修改值时,就会发生错误,如上面被注释的代码
可以看出 再借用的存在改变了原始借用的状态
,即便原始借用是可变的,在此时,也不能再随意对值进行变更了
结论:对可变引用创建不可变再借用时, 在不可变再引用生命周期内,原始的可变引用只可读取值,不可对值进行修改
2. 对可变引用创建可变再引用
再来看看第二种情况
fn main() {
let mut name = String::from("karima");
let borrow= &mut name;
let reborrow = &mut *borrow;
// println!("{}", borrow); // 此处将提示编译错误
println!("{}", reborrow);
println!("{}", borrow);
}
上述代码将 reborrow 设为可变的,此时在生命周期内即便是使用 println! 打印也会提示编译错误
结论:对可变引用创建可变再借用时,在可变再借用生命周期内,原始可变引用不可再使用
3. 对不可变引用创建不可变再引用
fn main() {
let name = String::from("karima");
let borrow= &name;
let reborrow = &*borrow;
println!("{}", borrow);
println!("{}", reborrow);
println!("{}", borrow);
}
此种方式相比于之前较为好理解 —— 不可变引用只能创建不可变再借用,且在不可变再借用期间使用原始可变引用的方法也只有读取,不可改变
结论:对不可变引用创建不可变再借用,在不可变再借用生命周期内,可以使用原始不可变引用读取值
再借用对于原始引用的影响取决于原始引用的可变性和再借用的可变性,三种情况下的规则总结如下
- 对可变引用创建不可变再借用:再借用生命周期内,原始引用只可读,不可写
- 对可变引用创建可变再借用:再借用生命周期内,原始引用不可使用
- 对不可变引用创建不可变再借用:再借用生命周期内,原始引用只可读
一个更完整的代码示例
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
impl Point {
fn move_to(&mut self, x: i32, y: i32) {
// self.x = x;
// self.y = y;
}
}
fn main() {
let mut p = Point { x: 0, y: 0 };
let r = &mut p;
let rr: &Point = &*r;
// r.move_to(10, 10); // 此处会提示编译错误
println!("{:?}", rr);
println!("{:?}", r);
}
上面我们编写了相对完整的代码,有一个结构体,获取不可变再借用,再尝试在再借用生命周期中调用方法,结果报错
开始分析
上述代码符合前面的第一条规则:对可变引用创建不可变再借用:再借用生命周期内,原始引用只可读,不可写
也就是在 rr 的生命周期内,只可读取值,不可修改值。但是 move_to
方法中获取了 可变引用
,即便在方法中可变引用并未使用到,并未更改原始结构体的任何内容,但是编译器是基于方法签名进行借用检查的,既然方法参数中有可变借用,那么就有可能使用可变借用,有可能进行修改,这是不被允许的。这是错误的主要原因
再借用的意义
前面分析和总结了再借用,那么在 rust 中为什么需要这样一个直觉上违法借用机制的东西呢?它的存在原因是什么?
再借用存在的重要原因是:在保证原始引用有效的情况下,获取一个临时的引用
关于其实际意义,可以用代码示例解释
#[derive(Debug)]
struct User {
name: String,
age: u8,
email: String
}
fn main() {
let mut user = User {
name: String::from("Alice"),
age: 30,
email: String::from("alice@example.com")
};
// 发生隐式再借用
let name_ref = &mut user.name;
let age_ref = &mut user.age;
*name_ref = String::from("Alicia"); // 修改名字
*age_ref += 1; // 修改年龄
println!("用户现在是 {} 岁的 {}", user.age, user.name);
}
文章开头的代码都是显式的创建了再借用 let reborrow = &*borrow;
,但是此处的代码由编译器隐式的创建了再借用,如下
// 其中的这行代码
let name_ref = &mut user.name; // 隐式再借用
// 实际上等价于
let temp_user_ref = &mut user; // 获取整个user的可变借用
let name_ref = &mut (*temp_user_ref).name; // 从借用中再借用字段
// 然后再使用再借用进行修改 ...
没有再借用和编译器的配合,我们就只能先获取结构体的引用,再获取属性引用,再修改,再释放 …
其他情况下存在的隐式再借用
fn main() {
let mut v = vec![1, 2, 3];
// 这个方法调用包含隐式再借用
v.push(4); // 编译器处理为(&mut v).push(4)
// 迭代器也涉及隐式再借用
for i in &mut v { // &mut v创建借用,迭代器进一步再借用每个元素
*i *= 2;
}
}
评论区