Rust所有权、枚举和模式匹配

认识所有权

所有权的三大核心规则

规则一:每个值都有一个所有者 (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),
    _ => (), // 忽略所有其他情况
}
*/
相关推荐
青梅主码2 小时前
SimilarWeb最新发布《全球电商行业报告2025》:美国、英国、日本等成熟经济体的电商市场已显现饱和迹象,访问量趋于下降
后端
rannn_1112 小时前
【SQL题解】力扣高频 SQL 50题|DAY2+3
数据库·后端·sql·leetcode
酸菜谭丶2 小时前
SpringBoot工程如何发布第三方Jar
spring boot·后端·jar
bybitq2 小时前
深入浅出 Go 流程控制:从循环到延迟执行
开发语言·后端·golang
chenyuhao20242 小时前
Linux系统编程:多线程互斥以及死锁问题
linux·运维·服务器·c++·后端
abap帅哥2 小时前
SAP MIRO/MIR4付款条件消失 :设计逻辑、根本原因与终极解决方案
数据库·后端·sap·abap·erp
Lisonseekpan2 小时前
Kafka、ActiveMQ、RabbitMQ、RocketMQ对比
java·后端·kafka·rabbitmq·rocketmq·activemq
回家路上绕了弯2 小时前
一文读懂分布式事务2PC:原理、流程与优缺点解析
后端
JaguarJack2 小时前
使用 Laravel Workflow 作为 MCP 工具提供给 AI 客户端
后端·php