Tailwind CSSTailwind CSS
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • 搜索未来:SEO与GEO双引擎实战手册
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • Rust 开发入门
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • 搜索未来:SEO与GEO双引擎实战手册
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • Rust 开发入门
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain

4.2 借用与引用

在上一节中,我们学习了 Rust 所有权的基本规则:每个值只有一个所有者,当所有者离开作用域时,值会被自动清理。这种设计虽然保证了内存安全,但在实际编程中,我们经常需要在不转移所有权的情况下访问数据。例如,你可能只想“查看”一个变量的值,而不想拥有它。为了实现这一点,Rust 引入了借用(Borrowing) 的概念,而借用的具体实现就是引用(Reference)。

什么是引用?

引用是 Rust 中一种特殊的指针类型,它允许你访问某个值,但并不拥有该值。你可以把引用想象成图书馆的“借书证”:有了借书证,你可以阅读这本书,但书的真正所有权仍然属于图书馆。当借书证过期(引用离开作用域)时,书依然在图书馆里,不会被销毁。

在语法上,创建一个引用使用 & 符号。例如:

let s1 = String::from("hello");
let len = calculate_length(&s1); // 传递 s1 的引用,而不是 s1 本身

fn calculate_length(s: &String) -> usize {
    s.len()
} // 这里,s 离开了作用域,但由于它只是引用,所以它指向的 String 不会被销毁

println!("The length of '{}' is {}.", s1, len); // s1 仍然有效

在这个例子中,&s1 创建了一个指向 s1 的引用。我们将这个引用传递给 calculate_length 函数。函数签名中的 s: &String 表明参数 s 是一个指向 String 类型的引用。

关键区别:

  • 所有权转移(Move):如果我们将 s1 直接传递给函数,s1 的所有权会转移到函数参数中,之后 s1 将不再可用。
  • 借用(Borrow):通过传递引用 &s1,我们只是“借出”了 s1 的使用权。函数结束后,借用结束,s1 的所有者仍然是原来的变量,因此可以继续使用。

引用的基本规则

引用虽然灵活,但必须遵守 Rust 编译器强制执行的规则,以确保内存安全。这些规则是 Rust 借用检查器的核心:

  1. 在任何给定时间,你只能拥有一个可变引用,或者任意数量的不可变引用。
  2. 引用必须始终有效(不能有悬垂引用)。

不可变引用

默认情况下,通过 & 创建的引用是不可变引用。这意味着你不能通过这个引用修改它指向的值。这就像你借了一本书,但你不能在书上做任何标记或修改内容。

let s = String::from("hello");
let r1 = &s;
let r2 = &s; // 可以有多个不可变引用

println!("{}, {}", r1, r2); // 正确:只读访问

你可以同时拥有多个不可变引用,因为它们只是读取数据,不会造成数据竞争。

可变引用

如果你需要修改借用的数据,可以使用可变引用,通过 &mut 创建。这就像你借了一本可写的工作手册,可以在上面做笔记。

let mut s = String::from("hello"); // 变量本身必须是可变的
let r = &mut s; // 创建一个可变引用
r.push_str(", world"); // 通过可变引用修改数据
println!("{}", r); // 输出: hello, world

可变引用的限制:

  • 同一作用域内,对于一个特定的数据,只能有一个可变引用。这是为了防止数据竞争(data race)。
let mut s = String::from("hello");

let r1 = &mut s;
let r2 = &mut s; // 编译错误:不能同时拥有两个可变引用

println!("{}, {}", r1, r2);
  • 你不能同时拥有一个可变引用和一个不可变引用。因为不可变引用假设数据不会改变,而可变引用可能会改变它。
let mut s = String::from("hello");

let r1 = &s; // 不可变引用
let r2 = &mut s; // 编译错误:不能同时拥有不可变引用和可变引用

println!("{}, {}", r1, r2);

为什么要有这些限制? 这些规则在编译时消除了数据竞争。数据竞争是一种非常难以追踪的 bug,通常发生在多线程或复杂的内存操作中。Rust 通过借用规则,在编译阶段就杜绝了这种可能性。

悬垂引用(Dangling References)

在 C 或 C++ 中,很容易创建指向已释放内存的指针,称为悬垂指针。Rust 编译器通过生命周期检查,确保引用永远不会变成悬垂引用。

例如,下面的代码会编译失败:

fn dangle() -> &String {
    let s = String::from("hello");
    &s // 返回 s 的引用
} // 这里 s 被销毁,其内存被释放。但引用仍然指向已释放的内存,这是危险的!

编译器会给出错误提示:“missing lifetime specifier”。因为函数返回的引用指向了函数内部创建的局部变量 s,而 s 在函数结束后就会被销毁。Rust 的借用检查器会阻止这种操作,确保引用始终指向有效的内存。

正确的做法是直接返回 String 本身,将所有权转移给调用者:

fn no_dangle() -> String {
    let s = String::from("hello");
    s // 返回 String,所有权转移
}

总结

借用和引用是 Rust 所有权系统的核心补充。它们允许你在不转移所有权的情况下安全地访问数据,同时通过编译器的严格检查,避免了悬垂指针和数据竞争等常见的内存安全问题。

  • 引用:使用 & 创建,允许你“借”用数据。
  • 不可变引用(&T):允许读取,不允许修改。可以有多个。
  • 可变引用(&mut T):允许读取和修改。同一时间只能有一个。
  • 规则:引用必须始终有效,且不能同时存在可变引用和不可变引用(或两个可变引用)指向同一数据。

掌握这些规则,你将能更灵活地编写 Rust 代码,同时享受 Rust 带来的内存安全保障。

Last Updated:: 5/9/26, 3:13 PM