Rust基础-part7-枚举、结构体

发布于:2025-07-30 ⋅ 阅读:(34) ⋅ 点赞:(0)

Rust基础[part7]_枚举、结构体

枚举

枚举用于表示一个值可能是多种变体(Variant)中的一种。每个变体可以有不同的数据类型:

enum Message {
    Quit,                   // 无数据
    Move { x: i32, y: i32 }, // 带命名数据(类似结构体)
    Write(String),          // 带单个值(类似元组)
    ChangeColor(i32, i32, i32), // 带多个值(类似元组)
}

基本使用

enum Pets {
    Cat(String),
    Dog { name: String, age: usize },
}

fn print_pet_info() {
    let a = Pets::Cat("Whiskers".to_string());
    let b = Pets::Dog {
        name: "Buddy".to_string(),
        age: 5,
    };

    // Debug trait allows us to print the enum variants
    println!("cat is {:?}", a);
    println!("dog is {:?}", b);
  
    //impl是用来为枚举或结构体定义方法的
    impl Pets {
        fn speak(&self) {
            println!("hi");
        }
    }
    a.speak();

    // 关联函数是与类型相关的函数,可以通过类型名直接调用
    // 关联函数不需要实例化对象就可以调用
    impl Pets {
        fn log(name: String) {
            println!("Logging pet: {}", name);
        }
    }

    Pets::log("Whiskers".to_string());
}

语法&规范

  • 语法

    • field-less enum
    • unit-only enum
  • 规范

    • Pascal Case

    • snake_case

用法

枚举通常与模式匹配(match)结合使用,以处理不同的变体:

match & if let
// match 语句:
    // 用于模式匹配,可以根据枚举的不同变体执行不同的代码
    match a {
        Pets::Cat(_) => {
            println!("This is a cat");
        }
        Pets::Bird => {
            println!("This is a bird");
        }
        Pets::Dog { name, age } => {
            println!("This is a dog named {} and age {}", name, age);
        }
        // 这里的 _ 是一个通配符,表示匹配所有未被前面分支匹配的情况
        _ => {
            println!("This is not a cat");
        }
    }


// if let 语句:
    // 用于简化模式匹配,当只关心某个特定变体时,可以使用 if let
    // 这种方式可以避免使用 match 的冗长语法
    if let Pets::Dog { name, age } = &b {
        println!("This is a dog named {} and age {}", name, age);
    } else {
        println!("This is not a dog");
    }
Option

Option 的作用是安全地表达“有值”或“没有值”的情况,避免空指针(null)带来的运行时错误。在 Rust 里没有 null,所有“可能为空”的场景都用 Option 来表达。

  • 你写的函数有时可能找不到结果、读取不到数据、或者参数可能为空,这时用 Option 返回 Some(值) 或 None。
  • 这样编译器会强制你检查“有没有值”,防止忘记判断导致程序崩溃。
fn option_example() {
    let some_value: Option<i32> = Some(42);
    let none_value: Option<i32> = None;
    // 使用 match 处理 Option
    match some_value {
        Some(val) => println!("Got a value: {}", val), // Some 分支
        None => println!("No value found"),            // None 分支
    }

    if let Some(val) = none_value {
        println!("Got a value: {}", val);
    } else {
        println!("No value found");
    }
}
Result

result 是一个枚举类型,用于表示操作的结果,Result<T, E> 有两个变体:Ok(T) 和 Err(E);Ok(T) 表示操作成功,并包含一个值 T,Err(E) 表示操作失败,并包含一个错误值 E;Result<T, E> 常用于处理可能失败的操作,例如文件读取、网络请求等

  • 可以提高代码的安全性和可读性
  • 可以避免异常处理(exception handling)的复杂性
fn result_example() {
    let ok_value: Result<i32, &str> = Ok(42);
    let err_value: Result<i32, &str> = Err("Error occurred");

    match ok_value {
        Ok(val) => println!("Got a value: {}", val), // Ok 分支
        Err(err) => println!("Error: {}", err),      // Err 分支
    }

    if let Err(err) = err_value {
        println!("Error: {}", err);
    }
}
Option & Result 转换

option-> result: ok_or()

fn option_result_convert() {
    let some_value: Option<i32> = Some(42);
    let none_value: Option<i32> = None;

    // Option 转 Result
    let result_from_some: Result<i32, &str> = some_value.ok_or("No value found");
    let result_from_none: Result<i32, &str> = none_value.ok_or("No value found");

    match result_from_some {
        Ok(val) => println!("Got a value: {}", val),
        Err(err) => println!("Error: {}", err),
    }

    match result_from_none {
        Ok(val) => println!("Got a value: {}", val),
        Err(err) => println!("Error: {}", err),
    }

}

result-> option: ok()

fn option_result_convert() {

    // Result 转 Option
    let ok_value: Result<i32, &str> = Ok(42);
    let err_value: Result<i32, &str> = Err("Error occurred");
    let option_from_ok: Option<i32> = ok_value.ok();
    let option_from_err: Option<i32> = err_value.ok();
    match option_from_ok {
        Some(val) => println!("Got a value: {}", val),
        None => println!("No value found"),
    }
    match option_from_err {
        Some(val) => println!("Got a value: {}", val),
        None => println!("No value found"),
    }
}
常见api示例
fn enum_api_example() {
    let option1 = Some(5);
    let option2: Option<i32> = None;

    let option_mapped = option1.map(|x: i32| x * 2); // 使用 map 方法对 Some 值进行操作
    println!("Option1 after map: {:?}", option_mapped); // 对 Some 值进行操作
    let option2_mapped = option2.map(|x: i32| x * 2); // 对 None 值进行操作时不会执行闭包
    println!("Option2 after map: {:?}", option2_mapped); // 对 None 值进行操作

    option1.and_then(|x: i32| Some(x * 2)); // and_then 方法用于链式调用
    option2.and_then(|x: i32| Some(x * 2)); // 对 None 值进行操作时不会执行闭包
    let option1_or_else = option1.or_else(|| Some(0)); // or_else 方法用于提供默认值
    println!("Option1 or else: {:?}", option1_or_else); // 对 Some 值提供默认值
    let option2_or_else = option2.or_else(|| Some(10)); // 对 None 值提供默认值
    println!("Option2 or else: {:?}", option2_or_else); // 对 None 值提供默认值
    option1.unwrap_or(0); // unwrap_or 方法用于获取 Some 值或提供默认值
    option2.unwrap_or(0); // 对 None 值提供默认值
    option1.is_some(); // 检查是否为 Some 值
    option2.is_some(); // 检查是否为 Some 值
    option1.is_none(); // 检查是否为 None 值

    let result: Result<i32, i32> = Ok(1);
    let error: Result<(), &str> = Err("An error occurred");
    result.map(|_| println!("Success!")); // 使用 map 方法对 Ok 值进行操作
    error.map(|_| println!("Success!")); // 对 Err 值进行操作时
    // 不会执行闭包
    result.and_then(|val| Ok(val)); // and_then 方法用于链式调用 返回的是result类型
    error.and_then(|val| Ok(val)); // 对 Err 值进行操作时不会执行闭包
    // ⚠️ or_else只会在 Err 分支执行
    result.or_else(|val: i32| Err(val)); // or_else 方法用于提供默认值
    error.or_else(|_| Err(())); // 对 Err 值提供默认值
    result.unwrap_or_else(|val: i32| 1); // unwrap_or_else 返回的是最终的值
    error.unwrap_or_else(|val| {
        println!("Error occurred: {}", val);
        ()
    }); // 对 Err 值提供默认值
    result.is_ok(); // 检查是否为 Ok 值
    error.is_ok(); // 检查是否为 Ok 值
    result.is_err(); // 检查是否为 Err 值
    error.is_err(); // 检查是否为 Err 值
}

⚠️ 坑

  • unwrap_or_else 得到的是最终的值(T)。
  • or_else 得到的是另一个 Option/Result(Option 或 Result<T, E>)。
  • or_else 只会在 Err 的时候调用闭包,而闭包参数 val 的类型应该是 E(也就是 Err 的类型),而不是 Ok 的类型

练习:

分析enum的内存大小

#[test]
fn enum_size_analysis() {
    use std::mem::size_of;

    enum MyEnum {
        A(u8, u8),
        B,
        C {},
    }
    enum EnumA1 {
        A = 255,
    }
    enum EnumA2 {
        A = 255,
        B,
    }

    println!("MyEnum size: {} bytes", size_of::<MyEnum>());
    println!("EnumA1 size: {} bytes", size_of::<EnumA1>());
    println!("EnumA2 size: {} bytes", size_of::<EnumA2>());
}

结构体

创建

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}
pub fn struct_example() {
    // 创建结构体实例
    // 1.每个字段都需要实例化
    // 2.字段顺序可以任意
    // 3.字段名逗号分隔
    let username = String::from("someusername123");
    let user1 = User {
        active: true,
        username, // 简写
        email: String::from("someone@example.com"),
        sign_in_count: 1,
    };

    let user2 = User {
        email: String::from("another@example.com"),
        ..user1 // 结构体更新语法,没有逗号
    };
}

常见使用

// 1. 访问字段
println!("The username is {}", user1.email);

//2 修改结构体字段(可变性)
  //      整个实例必须是可变的
  let mut user1 = User {
      active: true,
      username: String::from("someusername123"),
      email: String::from("someone@example.com"),
      sign_in_count: 1,
  };
  user1.email = String::from("anotheremail@example.com");


元祖结构体(tuple struct)


struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
//3 使用元组结构体
fn tuple_strut() {
    struct Color(i32, i32, i32);
    struct Point(i32, i32, i32);
    let black = Color(0, 0, 0);
    println!("{}", black.0);
}

单元结构体(unit-like struct)

// 单元结构体
fn unit_struct() {
    // 单元结构体创建
    let always_equal = AlwaysEqual;
    // 我们不关心AlwaysEqual实例中的任何内容,我们只关心其类型,然后可以为它实现某个特征
    impl SomeTrait for AlwaysEqual {
        // 函数体省略
    }
}

//单元结构体
struct AlwaysEqual;

所有权

{//3 所有权
    let user1 = User {
        active: true,
        username: String::from("someusername123"),
        email: String::from("someone@example.com"),
        sign_in_count: 1,
    };

    let email = user1.email;
    println!("{}", user1.email);// 所有权转移


    print_user(user1);// 这里也会报错
}

fn print_user(user: User) {
    println!("{:?}", user);
}
--------------------------------------------------
error[E0382]: borrow of moved value: `user1.email`
  --> src/struct_example.rs:44:20
   |
43 |     let email = user1.email;
   |                 ----------- value moved here
44 |     println!("{}", user1.email);// 所有权转移
   |                    ^^^^^^^^^^^ value borrowed here after move
  • 一旦结构体中某一个字段发生了所有权移动,那么整个结构体都会不能再赋值了
  • 使用没有实现copy特性的类型,都会发生所有权的转移。

定义方法

/**
 * 结构体方法的使用
 */

struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn new(width: u32, height: u32) -> Self {
              // new 方法可以创建一个 Self 的实例
        Self { width, height }
    }
    fn set_width(&mut self, width: u32) {
        self.width = width;
    }
    fn area(&self) -> u32 {
        self.width * self.height
    }

    fn is_wide(&self) -> bool {
        self.width > self.height
    }

    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}


#[test]
fn struct_function_example() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };
    println!(
        "The area of the rectangle is {} square pixels.",
        rect1.area()
    );
    let mut rect2 = Rectangle {
        width: 30,
        height: 50,
    };
    rect2.set_width(20);
    println!(
        "The area of the rectangle is {} square pixels.",
        rect2.area()
    );
  
    if rect2.is_wide() {
        println!("The rectangle is wide.");
    } else {
        println!("The rectangle is not wide.");
    }

    // 使用new
    let rect3 = Rectangle::new(30, 50);

    // can_hold
    println!(
        "The area of the rectangle is {} square pixels.",
        Rectangle::area(&rect3)
    );
}

实现trait

实现了通用的计算逻辑

trait Shape {
    fn area(&self) -> u32;
}

impl Shape for Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

trait Shape {
    fn area(&self) -> u32;
}

impl Shape for Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

impl Shape for Circle {
    fn area(&self) -> u32 {
        3 * self.radius * self.radius
    }
}

// 新建一个圆形
struct Circle {
    radius: u32,
}
impl Circle {
    fn new(radius: u32) -> Self {
        Circle { radius }
    }
}


/**
 * 通过impl Trait可以创建一个函数,
 * 这个函数的参数是任何实现了Shape trait的类型。
 * 这个函数可以传入圆形和矩形,都可以进行area计算,实现了通用的计算逻辑
 */
fn print_area(shape: &impl Shape) {
    println!("{}", shape.area());
}

#[test]
fn test_trait_impl() {
    // 使用trait通用计算
    let rect4 = Rectangle::new(30, 50);
    let circle1 = Circle::new(100);
    print_area(&rect4);
    print_area(&circle1);
}

自己实现Display

如果我们想打印出来整个矩形,该如何处理呢

use std::fmt::Display;

impl Display for Rectangle {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        for _ in 0..self.height {
            let mut s = String::new();
            for w in 0..self.width {
                s.push('6');
            }
            write!(f, "{}\n", s);
        }

        Ok(())
    }
}

#[test]
fn print_the_rectangle() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };
    println!("{}", rect1);
}

成功:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

自己实现debug

一般可以通过派生来实现debug

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

就可以使用"{:?}" 打印结构体

#[test]
fn print_the_rectangle() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    println!("{:?}", rect1);
}
----------------------------
Rectangle { width: 30, height: 50 }

不使用派生,自己实现:

use std::fmt::Debug;
impl Debug for Rectangle {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Rectangle: width: {}, height: {}",
            self.width, self.height
        );

        Ok(())
    }
}

网站公告

今日签到

点亮在社区的每一天
去签到