引用基础
> 理解引用(&T)如何借用值而不获取所有权,掌握不可变引用的基本用法。
为什么需要引用?
所有权的局限性
概念名称: 引用(Reference)允许借用值而不获取所有权。
语法结构:
┌──────────────────────────────────────┐
│ &T → 不可变引用(只读) │
│ &mut T → 可变引用(可读写) │
│ │
│ let r = &s; → 借用 s │
│ let r = &mut s; → 可变借用 s │
└──────────────────────────────────────┘最简示例
rust
▶ Runfn main() {
let s = String::from("hello");
let r = &s; // 借用,不移动所有权
println!("{}", r); // 通过引用访问
println!("{}", s); // s 仍然有效
}详细示例
rust
▶ Run// 问题:函数获取所有权后,原变量不能使用
fn main() {
let s = String::from("hello");
let len = string_length(s); // s 的所有权被移动
// println!("{}", s); // ❌ 错误:s 已无效
println!("长度:{}", len);
}
fn string_length(s: String) -> usize {
s.len()
}引用的解决方案
rust
▶ Run// 使用引用:不转移所有权
fn main() {
let s = String::from("hello");
let len = string_length(&s); // 只借用,不移动
println!("'{}' 的长度是 {}", s, len); // ✅ s 仍然有效
}
fn string_length(s: &String) -> usize {
s.len()
}引用 vs 所有权对比
┌─────────────────────────────────────────────────────┐
│ 所有权传递 vs 引用借用 │
├─────────────────────────────────────────────────────┤
│ │
│ 所有权传递(移动) │
│ ┌──────┐ ┌──────┐ │
│ │ main│ │函数 │ │
│ │ s │ ──────→ │ s │ │
│ │(无效)│ │(所有)│ │
│ └──────┘ └──────┘ │
│ ↑ ↓ │
│ 失去所有权 获得所有权 │
│ │
│ 引用借用 │
│ ┌──────┐ ┌──────┐ │
│ │ main│ │函数 │ │
│ │ s │ ──&s──→ │ &s │ │
│ │(所有)│ │(借用)│ │
│ └──────┘ └──────┘ │
│ ↑ ↓ │
│ 保持所有权 临时借用 │
│ │
└─────────────────────────────────────────────────────┘不可变引用(&T)
引用语法
概念名称: &T 创建不可变引用(只读借用)。
语法结构:
┌──────────────────────────────────────┐
│ let 引用名 = &变量; │
│ ↑ ↑ │
│ 名字 借用操作符 │
│ │
│ let r = &s; → r 借用 s │
│ fn f(x: &T) → 参数借用 │
│ &String::from() → 借用表达式结果 │
└──────────────────────────────────────┘最简示例
rust
▶ Runfn main() {
let s = String::from("hello");
let len = calculate_length(&s);
println!("'{}' 长度 {}", s, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}详细示例
rust
▶ Runfn main() {
let s = String::from("hello");
// 创建引用:使用 &
let r1 = &s;
let r2 = &s;
// 使用引用
println!("r1: {}", r1);
println!("r2: {}", r2);
// 原变量仍然有效
println!("s: {}", s);
}函数参数中的引用
rust
▶ Runfn main() {
let s = String::from("hello");
// 传递引用
let len = calculate_length(&s);
// s 仍然可用
println!("'{}' 的长度是 {}", s, len);
}
fn calculate_length(s: &String) -> usize {
// 通过引用访问数据
s.len()
} // s 离开作用域,但不释放内存内存布局图解
创建引用前:
栈(Stack) 堆(Heap)
┌─────────────┐ ┌─────────┐
│ s │ │"hello" │
│ ┌───┬───┬─┐ │ │ │
│ │ptr│len│cap│─┼─────────→│ │
│ └───┴───┴─┘ │ └─────────┘
└─────────────┘
创建引用 &s 后:
栈(Stack) 堆(Heap)
┌─────────────┐ ┌─────────┐
│ s │ │"hello" │
│ ┌───┬───┬─┐ │ │ │
│ │ptr│len│cap│─┼─────┬───→│ │
│ └───┴───┴─┘ │ │ └─────────┘
└─────────────┘ │
┌─────────────┐ │
│ &s (r1) │──────┘
│ ┌───┬───┬─┐ │
│ │ptr│len│cap│ │
│ └───┴───┴─┘ │
└─────────────┘
关键点:
• &s 也是一个(指针,长度,容量)结构
• 它指向 s 的数据,但不拥有它
• s 负责在作用域结束时释放数据多个不可变引用
rust
▶ Runfn main() {
let s = String::from("hello");
// 可以同时有多个不可变引用
let r1 = &s;
let r2 = &s;
let r3 = &s;
println!("r1: {}, r2: {}, r3: {}", r1, r2, r3);
// 原变量也可以使用
println!("s: {}", s);
}小结
- 引用(
&T)是"借用",不获取所有权,原变量仍可使用 - 多个不可变引用可以同时存在(只读访问)
- 函数参数使用引用可避免所有权转移
- 内存布局:引用指向数据但不拥有数据
练习题
详见:练习题