====== 第五章 引用与借用 ======
===== 5.1 引用的概念 =====
在Rust中,引用(Reference)允许你引用某个值而不获取其所有权。将创建一个引用的行为称为**借用(Borrowing)**。
==== 为什么需要引用 ====
回顾所有权转移的问题:
fn main() {
let s1 = String::from("hello");
let (s2, len) = calculate_length(s1);
// println!("{}", s1); // 错误!s1已被移动
println!("'{}'的长度是{}", s2, len);
}
fn calculate_length(s: String) -> (String, usize) {
let length = s.len();
(s, length)
}
这段代码很笨拙。使用引用可以优雅地解决:
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // 传递引用
println!("'{}'的长度是{}", s1, len); // s1仍然有效
}
fn calculate_length(s: &String) -> usize {
s.len()
} // s离开作用域,但它没有所有权,所以不释放内存
**图示:**
&String s 指向 String s1
+--------+ +---------+
| 指针 | ---> | hello |
| 长度 5 | | 容量 5 |
+--------+ +---------+
===== 5.2 引用规则 =====
Rust对引用有严格的规则,在编译期确保内存安全。
**基本规则:**
1. 在任一给定时刻,只能满足下列条件之一:
* 一个可变引用
* 任意数量的不可变引用
2. 引用必须总是有效的(不能悬垂)
===== 5.3 不可变引用 =====
==== 创建不可变引用 ====
let s = String::from("hello");
let r = &s; // 创建不可变引用
println!("{}", r); // 通过引用访问值
**可以同时存在多个不可变引用:**
let s = String::from("hello");
let r1 = &s;
let r2 = &s;
let r3 = &s;
println!("{}, {}, {}", r1, r2, r3); // 全部有效
**不可变引用不能修改数据:**
let s = String::from("hello");
let r = &s;
// r.push_str(", world"); // 错误!不可变引用不能修改数据
===== 5.4 可变引用 =====
==== 创建可变引用 ====
fn main() {
let mut s = String::from("hello");
change(&mut s);
println!("{}", s); // "hello, world"
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
**注意:**
* 变量本身必须是mut
* 引用也必须是&mut
* 函数参数类型必须是&mut T
==== 可变引用的限制 ====
**同一作用域只能有一个可变引用:**
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s; // 错误!不能同时有两个可变引用
println!("{}, {}", r1, r2);
**这个限制的好处:** 在编译期防止**数据竞争(Data Race)**。
**数据竞争的条件:**
1. 两个或更多指针同时访问同一数据
2. 至少有一个用于写入
3. 没有同步访问机制
==== 使用大括号创建新作用域 ====
let mut s = String::from("hello");
{
let r1 = &mut s;
println!("{}", r1);
} // r1在这里离开作用域
let r2 = &mut s; // 现在可以了
println!("{}", r2);
===== 5.5 混合引用规则 =====
**不能同时存在可变和不可变引用:**
let mut s = String::from("hello");
let r1 = &s; // 没问题
let r2 = &s; // 没问题
let r3 = &mut s; // 大问题!
println!("{}, {}, {}", r1, r2, r3);
**原因:** 不可变引用的用户不希望数据突然改变!
**但可以在不可变引用不再使用后创建可变引用:**
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{} 和 {}", r1, r2);
// r1和r2在这里之后不再使用
let r3 = &mut s; // 现在可以了
println!("{}", r3);
Rust编译器使用**非词法生命周期(Non-Lexical Lifetimes, NLL)**,可以检测到r1和r2在println!后就不再使用。
===== 5.6 悬垂引用(Dangling References) =====
悬垂指针(Dangling Pointer)是指向已经释放内存的指针。Rust编译器保证永远不会产生悬垂引用。
==== 悬垂引用示例 ====
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s // 返回对s的引用
} // s在这里离开作用域,内存被释放
// 返回的引用指向已释放内存!
编译器错误:
error[E0106]: missing lifetime specifier
==== 解决方案:返回所有权 ====
fn no_dangle() -> String {
let s = String::from("hello");
s // 转移所有权
}
===== 5.7 引用的本质 =====
==== 引用就是指针 ====
引用本质上是受约束的指针:
* 总是指向有效的内存
* 编译器检查其生命周期
* 保证遵循借用规则
**引用的大小:**
use std::mem;
fn main() {
println!("&i32大小:{}", mem::size_of::<&i32>());
println!("&String大小:{}", mem::size_of::<&String>());
println!("&[i32]大小:{}", mem::size_of::<&[i32]>());
}
// 在64位系统上,引用都是8字节
==== 自动解引用 ====
Rust在某些情况下会自动解引用:
let s = String::from("hello");
let r = &s;
// 这两种写法等价
println!("长度:{}", r.len()); // 自动解引用
println!("长度:{}", (*r).len()); // 显式解引用
===== 5.8 引用与函数参数 =====
==== 最佳实践 ====
**优先使用引用避免所有权转移:**
// 好的做法:使用引用
fn print_length(s: &String) {
println!("长度:{}", s.len());
}
// 不好的做法:转移所有权
fn print_length_bad(s: String) {
println!("长度:{}", s.len());
} // s在这里被释放,调用者不能再使用
**API设计原则:**
// 只读访问 -> &T
fn inspect(s: &String) {
println!("{}", s);
}
// 需要修改 -> &mut T
fn append_world(s: &mut String) {
s.push_str(" world");
}
// 需要获取所有权 -> T
fn take_ownership(s: String) {
println!("{} 被获取", s);
} // s在这里被释放
===== 5.9 多重引用与嵌套引用 =====
==== 引用的引用 ====
let x = 5;
let y = &x; // &i32
let z = &y; // &&i32
println!("z = {}", **z); // 需要两次解引用
==== 多重可变引用 ====
let mut x = 5;
let y = &mut x; // &mut i32
let z = &mut *y; // 通过y重新借用
*z = 10;
println!("x = {}", x);
===== 5.10 引用与模式匹配 =====
==== 匹配引用 ====
let x = Some(5);
match &x {
Some(val) => println!("值:{}", val),
None => println!("无值"),
}
==== ref关键字 ====
let x = 5;
let y = &x;
// 等价于
let ref z = x;
println!("z = {}", *z);
**在模式匹配中使用:**
let mut v = vec![1, 2, 3];
match v.get_mut(0) {
Some(ref mut val) => *val = 10,
None => (),
}
println!("{:?}", v); // [10, 2, 3]
===== 练习题 =====
==== 练习题5.1:交换两个数 ====
fn swap(a: &mut i32, b: &mut i32) {
let temp = *a;
*a = *b;
*b = temp;
}
fn main() {
let mut x = 5;
let mut y = 10;
println!("交换前:x = {}, y = {}", x, y);
swap(&mut x, &mut y);
println!("交换后:x = {}, y = {}", x, y);
}
==== 练习题5.2:字符串反转 ====
fn reverse(s: &mut String) {
let chars: Vec = s.chars().collect();
let reversed: String = chars.into_iter().rev().collect();
*s = reversed;
}
fn main() {
let mut s = String::from("hello");
println!("原字符串:{}", s);
reverse(&mut s);
println!("反转后:{}", s);
}
==== 练习题5.3:寻找最大值 ====
fn find_max(arr: &[i32]) -> Option<&i32> {
if arr.is_empty() {
return None;
}
let mut max = &arr[0];
for item in arr.iter() {
if item > max {
max = item;
}
}
Some(max)
}
fn main() {
let numbers = [3, 1, 4, 1, 5, 9, 2, 6];
match find_max(&numbers) {
Some(max) => println!("最大值是:{}", max),
None => println!("数组为空"),
}
}
==== 练习题5.4:修复借用错误 ====
下面代码有编译错误,请修复:
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &mut s;
println!("{}, {}", r1, r2);
}
**解决方案:**
fn main() {
let mut s = String::from("hello");
let r1 = &s;
println!("{}", r1);
// r1在这里之后不再使用
let r2 = &mut s;
println!("{}", r2);
}
===== 本章小结 =====
本章深入讲解了Rust的引用与借用机制:
* **引用(&T)**:借用值而不获取所有权
* **可变引用(&mut T)**:可以修改借用的值
* **借用规则**:
* 任意数量的不可变引用 **或** 一个可变引用
* 不能同时存在可变和不可变引用
* **悬垂引用**:Rust编译器保证引用总是有效
* **非词法生命周期**:编译器优化,更精确地追踪引用生命周期
理解引用与借用是掌握Rust的关键,这是Rust内存安全保证的核心机制。