Rust 的所有权(Ownership)是该语言的核心特性之一,是 Rust 无需垃圾回收器就能保证内存安全的关键机制。
所有权规则
Rust 所有权系统基于以下三条核心规则:
- 每个值都有一个变量作为其所有者:在 Rust 中,当你创建一个值(如整数、字符串等)时,必须将其绑定到一个变量上,这个变量就是该值的所有者。例如:
let s = String::from("hello");
这里变量 s
就是字符串 "hello"
的所有者。
2. 同一时间,一个值只能有一个所有者:这意味着一个值不能同时被多个变量直接拥有。例如,不能有两个变量同时完全拥有同一个 String
类型的值。
3. 3. 当所有者离开作用域时,值将被丢弃:作用域是程序中变量有效的范围。当变量离开其作用域时,Rust 会自动调用 drop
函数释放该值所占用的内存。例如:
{
let s = String::from("hello"); // s 在此作用域内有效
} // s 离开作用域,其占用的内存被释放
所有权转移
当把一个值赋值给另一个变量,或者将值作为参数传递给函数时,所有权会发生转移。这种机制避免了内存的双重释放问题。
赋值时所有权转移
let s1 = String::from("hello");
let s2 = s1; // 此时 s1 不再有效,所有权已转移到 s2
// println!("{}", s1); // 这行代码会报错
println!("{}", s2);
在这个例子中,当执行 let s2 = s1;
时,s1
对字符串的所有权转移到了 s2
,s1
不再拥有该字符串,后续使用 s1
会导致编译错误。
函数调用时所有权转移
fn take_ownership(s: String) {
println!("{}", s);
} // s 离开作用域,其占用的内存被释放
let s = String::from("hello");
take_ownership(s); // 此时 s 不再有效,所有权已转移到函数内部
// println!("{}", s); // 这行代码会报错
当调用 take_ownership(s)
时,s
的所有权转移到了函数的参数 s
上,函数执行完毕后,参数 s
离开作用域,其占用的内存被释放。 ### 引用与借用 为了在不转移所有权的情况下使用值,Rust 引入了引用(Reference)和借用(Borrowing)的概念。引用允许你使用值,但不拥有它。
不可变引用
fn calculate_length(s: &String) -> usize {
s.len()
} // 这里 s 是引用,不会释放其指向的值的内存
let s = String::from("hello");
let len = calculate_length(&s);
println!("The length of '{}' is {}.", s, len);
在这个例子中,&s
创建了 s
的一个不可变引用,传递给 calculate_length
函数。函数使用这个引用计算字符串的长度,而不获取其所有权。函数执行完毕后,s
仍然有效。
可变引用
可变引用允许你修改所引用的值,但有一些限制:同一时间,对于一个特定的数据,只能有一个可变引用,或者有任意数量的不可变引用,但不能同时拥有可变引用和不可变引用。
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
let mut s = String::from("hello");
change(&mut s);
println!("{}", s);
在这个例子中,&mut s
创建了 s
的一个可变引用,传递给 change
函数,函数可以修改 s
的值。 ### 切片类型 切片(Slice)是一种没有所有权的数据类型,它引用集合中的一部分元素。例如,字符串切片是 &str
类型:
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
println!("{} {}", hello, world);
在这个例子中,&s[0..5]
和 &s[6..11]
分别创建了字符串 s
的两个切片,它们引用了 s
中的部分字符,而不拥有这些字符。
通过所有权、引用和切片等机制,Rust 在编译时就能确保内存安全,避免了许多常见的内存错误,如空指针引用、悬垂指针和内存泄漏等。