核心番外-rust 可变性与所有权、借用的关系
可变性、所有权和借用
在 Rust 中,可变性(mutability)、所有权(ownership)和借用(borrowing)是三个核心概念,它们在一起决定了如何在内存中管理数据、避免数据竞争以及确保程序的安全性。这些概念互相联系,共同维护了 Rust 的内存安全性。
让我们逐一展开这些概念,并了解它们之间的关系。
1. 所有权(Ownership)
所有权是 Rust 的一个核心概念,它描述了每个值在程序中的拥有者。每个值在程序中只能有一个所有者,所有权在赋值、传参等操作时可以发生转移。所有权有以下规则:
- 每个值都有一个所有者(一个变量)。
- 每个值只能有一个所有者,在同一时间内不能有两个变量指向同一个值。
- 当所有者超出作用域时,值会被销毁,内存自动释放(没有垃圾回收机制)。
示例:
fn main() { let s1 = String::from("Hello"); // `s1` 拥有 String 的所有权 let s2 = s1; // `s2` 现在拥有所有权,`s1` 不再有效 // println!("{}", s1); // 错误:`s1` 已经不再有效 println!("{}", s2); // 输出:Hello }
在上面的例子中,s1
将所有权转移给了 s2
,因此 s1
不再有效。
2. 借用(Borrowing)
借用指的是让某个变量在不转移所有权的情况下被另一个变量使用。借用分为两种类型:不可变借用和可变借用。
(1) 不可变借用(Immutable Borrowing)
允许多个地方同时读取同一个值,但不允许修改该值。
示例:
fn main() { let s = String::from("Hello"); let s_ref1 = &s; // 不可变借用 let s_ref2 = &s; // 另一个不可变借用 println!("{}", s_ref1); // 输出:Hello println!("{}", s_ref2); // 输出:Hello }
在这个例子中,s_ref1
和 s_ref2
是 s
的不可变借用,它们都可以访问 s
的数据,但不能修改它。
(2) 可变借用(Mutable Borrowing)
允许一个地方修改借用的值,但在借用期间不能再有其他地方同时访问该值。
示例:
fn main() { let mut s = String::from("Hello"); let s_ref_mut = &mut s; // 可变借用 s_ref_mut.push_str(", world!"); // 修改 `s` println!("{}", s_ref_mut); // 输出:Hello, world! }
这里 s_ref_mut
是 s
的可变借用,它可以修改 s
的值。注意,在存在可变借用的期间,不能再有任何不可变借用或其他可变借用。
借用规则:
- 不可变借用允许多个不可变引用,但不允许可变借用或修改。
- 可变借用时,不能有任何不可变或其他可变借用。
3. 可变性(Mutability)
Rust 中的可变性分为变量可变性和数据可变性。
(1) 变量可变性
如果你想要修改一个变量绑定的值,需要使用 mut
关键字。默认情况下,Rust 中的变量是不可变的。
示例:
fn main() { let mut x = 5; // `x` 变量是可变的 println!("{}", x); // 输出:5 x = 10; // 修改 `x` println!("{}", x); // 输出:10 }
在这个例子中,x
是可变的,因此可以在后续修改它的值。
(2) 数据可变性
对于一个值的可变性,可以通过 mut
来决定数据本身是否可变。例如,对于一个 Vec
,你需要使用 mut
来表示该数据结构可变。
示例:
fn main() { let mut vec = vec![1, 2, 3]; vec.push(4); // 修改 vec,添加新元素 println!("{:?}", vec); // 输出:[1, 2, 3, 4] }
此时,vec
是可变的,因此可以对其进行修改。
4. 所有权、借用和可变性的关系
这三者密切相关,共同决定了 Rust 如何管理内存和避免数据竞争。我们总结它们之间的关系:
- 所有权:每个值只能有一个所有者,所有权的转移导致值的生命周期结束。
- 借用:借用允许多个地方使用数据,但不转移所有权。借用有不可变借用和可变借用之分:
- 不可变借用:允许多个地方读取数据,但不能修改。
- 可变借用:允许修改数据,但在借用期间不能有其他的借用(不可变或可变)。
- 可变性:如果你需要修改一个变量或数据,你必须声明其为可变的(
mut
)。
关键点:
- 在有可变借用时,不能同时存在不可变借用。
- 如果一个变量是可变的,那么它的所有权可以被修改。
- 借用不会转移所有权,借用的生命周期必须小于原所有者的生命周期。
5. 综合示例
fn main() { let mut s = String::from("Hello"); let s_ref1 = &s; // 不可变借用 let s_ref2 = &s; // 另一个不可变借用 println!("{}", s_ref1); // 输出:Hello println!("{}", s_ref2); // 输出:Hello let s_ref_mut = &mut s; // 可变借用(这会引发编译错误,因为有不可变借用) s_ref_mut.push_str(", world!"); // 编译错误 }
上面的例子会报错,因为我们在存在不可变借用的情况下尝试创建可变借用。Rust 编译器会阻止这种潜在的数据竞争和内存问题。
总结
- 所有权:每个值只能有一个所有者,所有权会在转移时改变,且在所有者生命周期结束时自动清理内存。
- 借用:借用允许数据的共享访问(不可变借用)或修改(可变借用),而不转移所有权。
- 可变性:
mut
关键字允许修改变量或数据的值,但借用的规则会对可变性产生影响。
值传递和引用借用
在 Rust 中,所有权系统确实以 按值传递 和 引用借用 为核心,这两个是最基本的所有权传递方式。但还有一些重要的补充概念需要注意,包括 可变借用 和 智能指针。
Rust 中的核心所有权传递方式
-
按值传递
- 默认情况下,函数参数会移动(转移)所有权。调用函数后,原始变量不可再使用。
-
不可变借用(
&T
)- 借用数据的只读访问权,不能修改被借用的数据。
-
可变借用(
&mut T
)- 借用数据的读写访问权,但一个时间点只允许一个可变借用。
-
智能指针(例如
Box<T>
、Rc<T>
、Arc<T>
)- 用于复杂的所有权管理场景,比如堆分配数据的共享和引用计数。
关于 *T
的使用
Rust 中是支持 *T
的用法,但这与原生指针和解引用操作相关。以下是关键点:
-
*T
代表裸指针(Raw Pointer)- 裸指针(如
*const T
和*mut T
)用于底层编程或与 C 接口交互。裸指针不受 Rust 所有权规则约束,但使用时需要极其小心,因为它可能导致未定义行为。 - 例如:
let x = 10; let ptr: *const i32 = &x; // 裸指针 unsafe { println!("{}", *ptr); // 解引用裸指针需要 `unsafe` 块 }
- 裸指针(如
-
*T
作为解引用操作符- 在 Rust 中,解引用操作符用于访问指针(引用或智能指针)指向的数据。
- 解引用引用:
let x = 42; let y = &x; assert_eq!(x, *y); // 解引用 `y` 得到 `x` 的值 - 解引用智能指针:
let boxed = Box::new(42); // 堆分配 assert_eq!(42, *boxed); // 解引用智能指针
-
*T
和所有权系统的关系*T
不常直接使用,因为 Rust 的所有权和引用规则更适合常见场景。- 除非需要底层操作或与 C/C++ 交互,否则推荐使用安全抽象(
&T
或Box<T>
等)。
总结
- Rust 的核心所有权模型:默认按值传递,通过
&T
或&mut T
借用数据,函数定义和调用需要明确匹配。 *T
是裸指针:主要用于底层编程或特殊需求,不推荐直接用于日常开发。- Rust 强烈鼓励使用安全的引用类型(
&T
、Box<T>
等),通过所有权系统和类型检查提供内存安全性。
虽然 Rust 借鉴了一些语言(如 C++),但它抛弃了裸指针的广泛使用,并用引用和智能指针代替,提供了更安全的所有权模型。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】