====== 第五章 引用与借用 ====== ===== 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内存安全保证的核心机制。