Appearance
12.2 借用
变量对其管理的内存拥有所有权。借用就是一个值的所有权在不发生转移的情况下,借给其他变量使用。
在 Rust 中,当你“借用”(borrowing)一个值时,你实际上是在创建一个指向该值的引用。 要使用借用,需要先使用引用语法:&
创建只读借用(不可变引用),&mut
创建可读写借用(可变引用)。 创建的引用只是拥有临时的使用权,而没有所有权。
因此,引用和借用虽然在术语上有所不同,但在实际使用中,它们描述的是同一个动作或状态:即一个值被另一个变量通过引用(或说“借用”)来访问。
Rust 的引用(借用指针)在编译后与底层机器指针(如 C 语言的指针)在内存表示上是一致的,唯一区别是 Rust 编译器通过借用检查器对其进行静态安全检查,确保引用始终有效且符合所有权规则。
Rust 借用的核心规则
在使用借用时,必须遵守以下核心规则(由编译器强制检查):
- 同一时间,你只能拥有:
- 一个可变引用(
&mut T
),或 - 任意多个不可变引用(
&T
),但不能同时拥有可变引用
- 一个可变引用(
- 所有引用必须有效:引用的生命周期不能超过其指向值的生命周期(即不能悬垂)
示例如下:
rust
fn foo(v: &Vec<i32>) {
v.push(5); // 错误:尝试通过不可变引用调用需要可变引用的方法
}
fn main() {
let v = vec![];
foo(&v);
}
这里会出现编译错误,信息为 “v
is a &
reference, so the data it refers to cannot be borrowed as mutable”。
原因在于 Vec::push
函数。它的作用是对动态数组添加元素,它的签名是:
rust
pub fn push(&mut self, value: T)
它要求 self 参数是一个 &mut Self
类型。而我们给 foo
传递的参数是 &Vec
类型,因此会报错。修复方式如下:
rust
// 需要接收可变引用参数
fn foo(v: &mut Vec<i32>) {
v.push(5); // 正确:通过可变引用修改 Vec
}
fn main() {
// 1. 变量本身必须声明为可变(允许修改其内容)
let mut v = vec![];
// 2. 调用时获取可变引用
foo(&mut v);
// 打印结果,v 已被修改
println!("{:?}", v); // 输出:[5]
}
对于 &mut
型指针,请大家注意不要混淆它与变量绑定之间的语法。 如果 mut
修饰的是变量名,那么它代表这个变量可以被重新绑定;如果 mut 修饰的是“借用指针 &
”,那么它代表的是被指向的对象可以被修改。
示例如下:
rust
fn main() {
let mut var = 0_i32; // var 是可变变量(可重新赋值)
{
let p1 = &mut var; // p1 是不可变变量(不能重新赋值),但指向可变内容
*p1 = 1; // 可以通过 p1 修改 var 的值
// p1 = &mut 42; // 错误:p1 变量本身不可变,不能重新赋值
}
{
let temp = 2_i32;
let mut p2 = &var; // p2 是可变变量(可以重新赋值),但指向不可变内容
// *p2 = 3; // 错误:p2 是不可变引用,不能修改指向内容
p2 = &temp; // 正确:p2 变量本身可变,可以重新指向其他值
}
{
let mut temp = 3_i32;
let mut p3 = &mut var; // p3 是可变变量(可重新赋值),且指向可变内容
*p3 = 3; // 可以修改指向内容
p3 = &mut temp; // 可以重新指向其他可变内容
}
}
借用指针在编译后,实际上就是一个普通的指针,它的意义只能在编译阶段的静态检查中体现。 这种编译阶段的检查正是 Rust 内存安全保障的核心机制之一,通过严格的借用规则,编译器能够在编译时就防止诸如悬垂指针和数据竞争等常见的内存安全问题。