认识所有权
所有权的三大核心规则
规则一:每个值都有一个所有者 (Owner)
- 在 Rust 中,内存中的每一个值(数据)都由一个唯一的变量(所有者)负责管理。
- 当所有者离开其作用域时,该值所占用的内存就会被自动、立即地释放(调用 drop 函数)。这被称为 RAII(Resource Acquisition Is Initialization,资源获取即初始化)原则。
rust
fn main() {
let s1 = String::from("hello"); // s1 是 "hello" 字符串数据的所有者
// ... s1 离开作用域时,内存被释放
}
规则二:一个值在任何时间点只能有一个所有者
- 这是所有权的核心。当所有权被转移(移动)后,原来的变量就 失效(Invalidated) 了。
- 这避免了多个指针同时指向同一块内存,防止了**双重释放(Double Free)**错误。
示例:所有权转移(Move) 当我们将一个 String 类型的值赋给另一个变量时,所有权会发生转移。
rust
let s1 = String::from("hello"); // s1 是所有者
// 所有权从 s1 转移到 s2。s1 不再有效!
let s2 = s1;
// 尝试使用 s1 会导致编译错误!
// println!("s1: {}", s1); // <-- 编译错误:借用已经移动的值
规则三:当所有者离开作用域时,值会被丢弃 (Drop)
- 作用域(Scope)是程序中一个有效的变量范围,通常由一对花括号 {} 定义。
- 当变量超出其作用域时,Rust 会自动调用其关联的 drop 函数来释放内存。
rust
fn main() {
{ // s 是在这个作用域中被创建的
let s = String::from("scope");
// ...
} // s 在这里超出作用域,"scope" 占用的内存被释放
}
解决所有权限制:借用 (Borrowing)
由于所有权转移(Move)太严格,Rust 引入了 借用(Borrowing) 机制来让你在不转移所有权的情况下临时访问数据。
借用通过 引用(References) 来实现。
1. 不可变借用(Immutable Borrow)
- 在任何给定时间,您可以拥有任意数量的不可变引用。
- 借用方只能读取数据,不能修改。
rust
let s = String::from("data");
let r1 = &s; // 不可变借用 r1
let r2 = &s; // 另一个不可变借用 r2
println!("r1: {}, r2: {}", r1, r2);
// s 仍然是所有者,且所有引用都只能读取 s
2. 可变借用(Mutable Borrow)
- 通过 &mut 符号创建引用。
- 在任何给定时间,您只能拥有一个可变引用。
- 借用方可以修改数据。
示例:可变借用
rust
let mut s = String::from("original");
let r1 = &mut s; // 可变借用 r1
r1.push_str(" changed"); // 允许修改
// 下面这行会导致编译错误,因为在 r1 存在时,不能创建第二个可变引用:
// let r2 = &mut s; // <-- 编译错误!
println!("s (通过 r1 修改): {}", r1);
// r1 离开作用域后,可以再次借用 s
let r2 = &mut s;
r2.push_str(" again");
关键原则:数据竞争的预防
所有权和借用规则的最终目标是防止数据竞争(Data Races),这是并发编程中最危险的错误之一。
数据竞争的三个条件:
- 两个或更多指针同时访问同一数据。
- 其中至少有一个指针用于写入数据。
- 没有同步机制来控制对数据的访问。
Rust 的借用规则(即 "同时只能有一个可变引用,或者任意数量的不可变引用")确保了这三个条件永远不会在编译时成立,从而保证了线程安全和内存安全。
使用结构体组织相关联的数据
Rust 结构体有三种主要的形式:经典结构体、元组结构体 和 单元结构体。
1. 经典结构体 (Classic Struct)
A. 定义结构体
rust
// 定义一个名为 'User' 的结构体
struct User {
// 字段名: 字段类型
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
B. 实例化结构体
rust
fn main() {
// 使用字面量语法实例化结构体
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("user123"),
active: true,
sign_in_count: 1,
};
println!("User: {}, Email: {}", user1.username, user1.email);
// 注意:默认情况下,结构体的实例是不可变的
// user1.active = false; // <-- 编译错误!
// 如果需要修改实例,必须将整个实例声明为可变 (mut)
let mut user2 = User {
email: String::from("another@example.com"),
username: String::from("another_user"),
active: false,
sign_in_count: 5,
};
// 现在可以修改字段了
user2.active = true;
user2.sign_in_count += 1;
}
C. 结构体更新语法 (Struct Update Syntax)
rust
fn main() {
// ... user1 已定义
let user3 = User {
email: String::from("new_email@example.com"),
// 使用 user1 实例中剩余字段的值
// 注意:user1 必须仍然有效(如果字段是 Copy 类型,则不会移动所有权)
..user1
};
// 如果 user1 包含非 Copy 类型(如 String),user1 在这里会部分失效。
// 由于 user1 的所有权部分被转移到了 user3,这里不能再使用 user1。
// println!("{}", user1.username); // <-- 编译错误,所有权已转移
}
2. 元组结构体 (Tuple Structs)
rust
// 定义:一个 Color 结构体,包含三个 u8 字段
struct Color(u8, u8, u8);
// 定义:一个 Point 结构体,包含三个 f64 字段
struct Point(f64, f64, f64);
fn main() {
let black = Color(0, 0, 0); // 实例化
let origin = Point(0.0, 0.0, 0.0); // 实例化
// 访问字段使用索引 (与元组相同)
println!("R value: {}", black.0);
println!("X value: {}", origin.0);
// 尽管它们内部结构相同 (三个数字),但 Rust 认为 Color 和 Point 是不同的类型
// let x = black.0 + origin.0; // 即使数值类型相同,也不能直接混用,因为类型不同
}
3. 单元结构体 (Unit-Like Structs)
rust
// 定义:一个没有任何数据的结构体
struct Marker;
fn main() {
// 实例化:无需使用括号
let subject = Marker;
// 单元结构体通常用于泛型编程或标记类型
}
方法语法(Method Syntax)允许您在结构体(Struct)、枚举(Enum)或 Trait 对象上定义函数。这些函数被称为方法(Methods)
1. 方法的定义和实现
rust
// 1. 定义结构体
#[derive(Debug)] // 加上这个 trait 方便打印
struct Rectangle {
width: u32,
height: u32,
}
// 2. 使用 impl 块实现方法
impl Rectangle {
// 方法 1: 计算面积的方法
// 首个参数是 &self,表示该方法借用了 Rectangle 实例的不可变引用
// 这意味着调用此方法不会消耗或改变 self
fn area(&self) -> u32 {
self.width * self.height
}
// 方法 2: 判断当前矩形是否能容纳另一个矩形
// 接受另一个 Rectangle 实例的不可变引用作为参数
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
// 方法 3: 构造函数(关联函数)
// 注意:这不是方法,因为它没有 self 参数。
// 在 Rust 中,通常使用这种关联函数来创建实例,类似于静态方法。
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
let rect2 = Rectangle { width: 10, height: 40 };
let rect3 = Rectangle::square(25); // 使用关联函数创建实例
// 1. 调用方法
// Rust 自动进行引用和解引用:rect1.area() 实际上是 (&rect1).area()
println!("矩形 1 的面积是: {}", rect1.area());
// 2. 调用带参数的方法
println!("矩形 1 能否容纳矩形 2? {}", rect1.can_hold(&rect2)); // true
println!("矩形 1 能否容纳矩形 3? {}", rect1.can_hold(&rect3)); // false
// 3. 打印使用关联函数创建的实例
println!("矩形 3 (正方形): {:?}", rect3);
}
2. self 参数的几种形式

rust
struct Counter {
value: u32,
}
impl Counter {
// 使用 &mut self: 修改实例状态
fn increment(&mut self) {
self.value += 1;
}
// 使用 self: 消耗所有权,通常用于将自身转换为另一种类型
fn reset_and_consume(self) -> u32 {
// self 在这里被消耗,函数返回后,外部的 Counter 实例不能再被使用
self.value
}
}
fn main() {
let mut my_counter = Counter { value: 5 };
// 使用 &mut self
my_counter.increment(); // value 现在是 6
println!("计数器值: {}", my_counter.value); // 仍然可以使用
// 使用 self (消耗所有权)
let final_value = my_counter.reset_and_consume();
println!("最终值: {}", final_value);
// println!("{}", my_counter.value); // <-- 编译错误!my_counter 所有权已转移并失效
}
3. 关联函数 (Associated Functions)
关联函数是不将实例(self)作为第一个参数的函数。它们与结构体关联,但被称为函数 而不是方法。
- 调用方式: 使用
::(双冒号,例如StructName::function_name(...))。 - 主要用途: 充当构造函数,用来创建结构体的新实例。
枚举和模式匹配
1. 枚举 (Enums: Enumerations)
A. 定义枚举
rust
// 定义一个名为 'Message' 的枚举
enum Message {
Quit, // 变体 1: 不携带任何数据
Move { x: i32, y: i32 }, // 变体 2: 携带一个匿名结构体
Write(String), // 变体 3: 携带一个 String
ChangeColor(i32, i32, i32), // 变体 4: 携带三个 i32 (元组)
}
B. 实例化枚举
rust
fn main() {
// 实例化不同的变体
let q = Message::Quit;
let m = Message::Move { x: 10, y: 5 };
let w = Message::Write(String::from("hello"));
let c = Message::ChangeColor(0, 160, 255);
}
C. 核心用例
Option<T>
rust
enum Option<T> {
None, // 变体 1: 表示没有值 (相当于 null)
Some(T), // 变体 2: 表示有一个值,且类型为 T
}
2. 模式匹配 (Pattern Matching)
A. match 表达式
rust
fn handle_message(msg: Message) {
// 使用 match 来匹配 Message 实例 msg 的不同变体
match msg {
Message::Quit => {
println!("Quit 命令:程序将退出。");
}
// 解构 Move 变体,将内部的 x 和 y 字段提取出来
Message::Move { x, y } => {
println!("Move 命令:移动到 (x={}, y={})", x, y);
}
// 解构 Write 变体,将内部的 String 提取出来
Message::Write(text) => {
println!("Write 命令:写入文本 '{}'", text);
}
// 解构 ChangeColor 变体,将内部的 R, G, B 值提取出来
Message::ChangeColor(r, g, b) => {
println!("ChangeColor 命令:颜色设置为 R={}, G={}, B={}", r, g, b);
}
}
}
B. 匹配的强大功能
- 绑定值: 匹配允许将内部数据绑定到新的变量名(如上面的 x, y, text)。
- 穷尽性检查: 如果您遗漏了 Message 的任何一个变体,编译器都会报错。
C. 通配符 (_)
rust
let dice_roll = 9;
match dice_roll {
3 => println!("得分 3!"),
7 => println!("得分 7!"),
// _ 匹配任何其他值,不绑定任何变量
_ => println!("继续游戏..."),
}
D. if let (简洁的单模式匹配)
rust
// 我们有一个可选的数字
let config_max: Option<i32> = Some(3);
// 如果是 Some(max) 则执行代码,否则忽略
if let Some(max) = config_max {
println!("配置的最大值是: {}", max);
}
// 相当于:
/*
match config_max {
Some(max) => println!("配置的最大值是: {}", max),
_ => (), // 忽略所有其他情况
}
*/