序
Rust 最独特的地方之一就是它的 所有权(Ownership) 系统。
它主要用于管理内存和资源的安全性,防止内存泄漏和数据竞争。
通过这一系统,Rust 不需要垃圾回收器来管理内存,而是通过编译时的检查确保内存安全。
所有权规则
Rust 的所有权规则有三条核心原则:
- 每个值在 Rust 中都有一个所有者。
- 每个值在同一时间只能有一个所有者。
- 当所有者离开作用域时,值会被丢弃,内存也会自动释放。
示例一
fn main() {
let s1 = String::from("hello"); // s1 是所有者
let s2 = s1; // s1 的所有权被转移给 s2
// println!("{}", s1); // 编译错误,s1 不再拥有该值
println!("{}", s2); // 正常输出
}
这里的赋值操作会导致所有权的转移。
s1的所有权被转移到s2,之后s1不再有效。
示例二
let s1 = String::from("hello");
fn arr_self (s: String) {
println!("{:?}", s);
}
arr_self(s1);
println!("{:?}", s1); // 不能再使用s1
函数参数传递也可以看作是一个赋值过程。
这里s1的所有权被转移给函数arr_self,之后 s1 不再有效。
Move vs Copy
在 Rust 中,不是所有类型的值都会在所有权转移时被“移动”(Move),有些类型会被“复制”(Copy)
Copy 类型
像 i32、bool、char 等大小固定、简单的类型可以实现 Copy 特性。
它们在赋值或传参时会被复制,而不是移动。这些类型不涉及所有权转移。
示例一
let x = 5;
let y = x; // x 被复制,而不是移动
println!("x: {}, y: {}", x, y); // 两者都可以使用
为什么 i32 等类型看起来像没有所有者 ?
正是因为这个 Copy 机制,它们在赋值或传递时不会转移所有权,而是直接复制。
因此,它们在赋值时没有表现出显式的所有权转移行为,给人的感觉是“它们没有所有者”。
回到所有权的三条规则
-
每个值在 Rust 中都有一个所有者
适用于所有类型的,也包括 i32、bool 等。即使它们可以被复制,每个副本也有自己的所有者。
-
每个值在同一时间只能有一个所有者
对于 Copy 类型,它们在被复制后,副本与原本是独立的,因此每个副本都有自己的所有者。
-
当所有者离开作用域时,值会被丢弃
对于 Copy 类型,它们会在离开作用域时被删除。由于它们是栈上的小数据,不涉及堆内存的管理;对于堆分配的数据类型,Rust 会自动释放它们的内存。
示例二
let arr = [1, 2, 3, 4, 5];
fn arr_self (arr: [i32; 5]) {
println!("{:?}", arr);
}
arr_self(arr);
println!("{:?}", arr); // 有效
对于类型 [i32; 5] 来说,它是一个固定大小的数组,实现了 Copy 特性。
因此,当传递给函数 arr_self 时,会发生复制,而不是所有权转移。
所以,arr 在函数内部和外部都是有效的。
Move 类型
像 String、Vec 等堆上分配的类型,
在赋值或传参时会发生所有权转移(move)。原来的变量将失效。
示例一
let s1 = String::from("hello");
let s2 = s1; // s1 的所有权被转移给 s2
// println!("{}", s1); // 错误:s1 不再有效
println!("{}", s2);
示例二
let arr = [
String::from("hello"),
String::from("world"),
];
fn arr_self (arr: [String; 2]) {
println!("{:?}", arr);
}
arr_self(arr);
println!("{:?}", arr); // 编译错误
对于类型 [String; 2] 来说,它是一个固定大小的数组。
但它的元素是 String 类型,它们在堆上分配内存,因此不实现 Copy 特性。
避免所有权转移
引用
如果你不想转移所有权,而是希望多个变量共享同一个值,可以使用引用(Reference)。
通过 & 来创建引用,默认引用是不可变的,可以通过 &mut 创建可变引用。
不可变引用
示例一
fn main() {
let s1 = String::from("hello");
let s2 = &s1; // 引用 s1 的值
println!("s1: {}, s2: {}", s1, s2); // s1 和 s2 都能正常使用
}
示例二
let s1 = "hello"; // &str
let s2 = s1;
println!("s1: {}, s2: {}", s1, s2); // s1 和 s2 都能正常使用
&str 是字符串字面量,也是一个不可变引用。
它不拥有数据,只是引用常量字符串,因此不涉及所有权的问题。
可变引用
示例一
fn main() {
let mut s1 = String::from("hello");
let s2 = &mut s1; // 可变引用
s2.push_str(", world");
println!("s2: {}", s2); // s2: hello, world
println!("s1: {}", s1); // s1: hello, world
}
注意:这里
s1的打印不能在s2之前, 这涉及到 Rust 的借用规则,将在下一节中提及
解引用
如果你想要访问引用所指向的值,可以使用**解引用(Dereference)**操作符 *
fn main() {
let mut x = 10; // 可变的 i32 值
let y = &mut x; // 创建一个对 x 的可变引用
*y = *y + 5; // 解引用并修改 y 所指向的值
println!("Value of x: {}", x); // 输出 15
}
y 为 x 的可变引用, *y (解引用 y) 实际上是对 x 的直接访问
解引用也不会改变所有权, 因此原始数据的所有权保持不变属于 x
结
-
所有权系统 是 Rust 语言的一个核心特性,旨在通过严格的规则在编译时捕获潜在的内存问题
-
转移 和 复制 是 Rust 数据传递的两种机制。
Copy类型不会引起所有权转移,而是会复制数据 -
引用 允许多个地方共享同一数据,能够避免转移所有权,从而减少性能开销和内存安全问题