什么是所有权?
所有权是 Rust 的核心特性之一,它决定了如何管理内存以及何时释放内存。Rust 使用所有权系统来保证程序的安全性和高效性,而无需像 C 或 C++ 那样依赖于手动管理内存。所有权系统由以下几部分组成:
- 所有权
- 借用
- 生命周期
所有权规则
Rust 有三个核心的所有权规则,理解这些规则对于编写有效的 Rust 代码至关重要:
- 在一个作用域内的每一个值都有一个所有者。
- 值在同一时间只能有一个所有者。
- 当所有者离开作用域,这个值将被丢弃。
所有权与变量绑定
当一个值被赋予一个变量时,这个变量就成为了该值的所有者。例如:
fn main() {
let s = "hello"; // `s` 现在是字符串字面量的所有者
}在这个例子中,s 是字符串字面量 "hello" 的所有者。一旦 s 离开作用域,这个字符串字面量就会被丢弃。然而,字符串字面量实际上是在编译时被静态分配的内存,因此不会真正地被丢弃。
所有权转移
当一个值赋给另一个变量时,原来的变量不再拥有这个值。这被称为所有权转移。例如:
fn main() {
let s1 = String::from("hello");
let s2 = s1; // 所有权从 `s1` 转移到了 `s2`
println!("{}", s2); // 正确
// println!("{}", s1); // 错误:使用已转移所有权的变量
}在上面的例子中,s1 将其拥有的 String 值转移给了 s2。之后尝试访问 s1 将导致编译错误,因为 s1 已经失去了对 String 的所有权。
所有权与函数
当一个值作为参数传递给函数时,该值的所有权也会转移给函数。例如:
// www.javascriptcn.com code example
fn main() {
let s = String::from("hello");
takes_ownership(s); // `s` 的所有权被转移给函数
let x = 5;
makes_copy(x); // `x` 的值被复制到函数中
}
fn takes_ownership(some_string: String) { // some_string 进入作用域
println!("{}", some_string);
} // some_string 离开作用域,因此 `some_string` 被丢弃
fn makes_copy(some_integer: i32) { // some_integer 进入作用域
println!("{}", some_integer);
} // some_integer 离开作用域,但是因为它是实现了 Copy trait 的类型,所以不会被丢弃在这个例子中,s 的所有权被传递给 takes_ownership 函数,因此在函数内部可以安全地使用 s。而在 makes_copy 函数中,由于 i32 类型实现了 Copy trait,它的值被复制了一份传递给函数,因此原始值 x 仍然有效。
返回值与所有权
函数也可以返回一个值的所有权。例如:
// www.javascriptcn.com code example
fn main() {
let s1 = gives_ownership(); // `gives_ownership` 将其返回值的所有权转移给 `s1`
let s2 = String::from("hello");
let s3 = takes_and_gives_back(s2); // `s2` 的所有权被转移给 `takes_and_gives_back` 函数,然后又转移给 `s3`
}
fn gives_ownership() -> String { // `gives_ownership` 将其返回值的所有权转移给调用者
let some_string = String::from("hello");
some_string
}
fn takes_and_gives_back(a_string: String) -> String { // `a_string` 的所有权被转移给 `takes_and_gives_back` 函数
a_string
}在这个例子中,gives_ownership 函数创建了一个新的 String 并将其所有权转移给调用者。takes_and_gives_back 函数接受一个 String 参数,并将其所有权再次转移给调用者。
引用与借用
引用允许我们访问一个值而不获取其所有权。这有助于避免所有权转移带来的复杂性,并且可以在不转移所有权的情况下共享数据。引用分为不可变引用和可变引用两种类型。
不可变引用
不可变引用通过 & 符号来创建。例如:
// www.javascriptcn.com code example
fn main() {
let s = String::from("hello");
let len = calculate_length(&s); // 创建一个不可变引用
println!("The length of '{}' is {}.", s, len);
}
fn calculate_length(s: &String) -> usize { // `s` 是一个指向 `String` 的不可变引用
s.len()
}在这个例子中,calculate_length 函数接受一个不可变引用作为参数。这允许我们在不转移所有权的情况下计算 String 的长度。
可变引用
可变引用通过 &mut 符号来创建。一个值在任何时刻只能有一个可变引用。例如:
// www.javascriptcn.com code example
fn main() {
let mut s = String::from("hello");
change(&mut s); // 创建一个可变引用
println!("{}", s);
}
fn change(some_string: &mut String) { // `some_string` 是一个指向 `String` 的可变引用
some_string.push_str(", world");
}在这个例子中,change 函数接受一个可变引用作为参数。这允许我们在不转移所有权的情况下修改 String 的内容。
生命周期
生命周期确保引用总是有效的。Rust 编译器会自动推断大多数引用的生命周期,但在某些情况下需要显式指定。生命周期注解通常放在引用类型的前面。例如:
// www.javascriptcn.com code example
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { // `'a` 是一个生命周期注解
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let string1 = String::from("long string is long");
{
let string2 = String::from("xyz");
let result = longest(string1.as_str(), string2.as_str());
println!("The longest string is {}", result);
}
}在这个例子中,longest 函数接受两个引用,并返回一个引用。生命周期注解 'a 确保返回的引用始终有效。
总结
本章介绍了 Rust 的所有权系统,包括所有权、借用和生命周期的概念。理解这些概念对于编写高效的 Rust 代码至关重要。通过所有权转移、引用和生命周期注解,Rust 提供了一种安全且高效的方式来管理内存。