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),
    _ => (), // 忽略所有其他情况
}
*/
相关推荐
神奇小汤圆3 小时前
Unsafe魔法类深度解析:Java底层操作的终极指南
后端
神奇小汤圆4 小时前
浅析二叉树、B树、B+树和MySQL索引底层原理
后端
文艺理科生4 小时前
Nginx 路径映射深度解析:从本地开发到生产交付的底层哲学
前端·后端·架构
千寻girling4 小时前
主管:”人家 Node 框架都用 Nest.js 了 , 你怎么还在用 Express ?“
前端·后端·面试
南极企鹅4 小时前
springBoot项目有几个端口
java·spring boot·后端
Luke君607974 小时前
Spring Flux方法总结
后端
define95274 小时前
高版本 MySQL 驱动的 DNS 陷阱
后端
忧郁的Mr.Li5 小时前
SpringBoot中实现多数据源配置
java·spring boot·后端
暮色妖娆丶6 小时前
SpringBoot 启动流程源码分析 ~ 它其实不复杂
spring boot·后端·spring
Coder_Boy_6 小时前
Deeplearning4j+ Spring Boot 电商用户复购预测案例中相关概念
java·人工智能·spring boot·后端·spring