Rust入门:结构体与枚举

结构体(struct)和枚举(enum)是Rust中组织数据的两种基本方式,配合模式匹配能写出表达力很强的代码。

struct定义

struct User {
    name: String,
    email: String,
    age: u32,
    active: bool,
}

fn main() {
    let user = User {
        name: String::from("Alice"),
        email: String::from("alice@example.com"),
        age: 28,
        active: true,
    };
    println!("{} ({})", user.name, user.email);
}

字段初始化简写——变量名和字段名相同时可以省略:

fn new_user(name: String, email: String) -> User {
    User {
        name,     // 等同于 name: name
        email,    // 等同于 email: email
        age: 0,
        active: true,
    }
}

结构体更新语法:

let user2 = User {
    email: String::from("bob@example.com"),
    ..user  // 其余字段从user复制
};

impl:为结构体添加方法

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

impl Rectangle {
    // 关联函数(没有self参数),类似静态方法
    fn new(width: f64, height: f64) -> Self {
        Rectangle { width, height }
    }
    
    fn square(size: f64) -> Self {
        Rectangle { width: size, height: size }
    }
    
    // 方法(有&self参数)
    fn area(&self) -> f64 {
        self.width * self.height
    }
    
    fn perimeter(&self) -> f64 {
        2.0 * (self.width + self.height)
    }
    
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
    
    // 可变借用
    fn scale(&mut self, factor: f64) {
        self.width *= factor;
        self.height *= factor;
    }
}

fn main() {
    let mut rect = Rectangle::new(10.0, 5.0);
    println!("Area: {}", rect.area());           // 50.0
    println!("Perimeter: {}", rect.perimeter());  // 30.0
    
    rect.scale(2.0);
    println!("Scaled area: {}", rect.area());     // 200.0
    
    let small = Rectangle::square(3.0);
    println!("Can hold? {}", rect.can_hold(&small)); // true
}

元组结构体

没有字段名的结构体,适合简单的类型包装:

struct Color(u8, u8, u8);
struct Point(f64, f64, f64);

fn main() {
    let red = Color(255, 0, 0);
    let origin = Point(0.0, 0.0, 0.0);
    
    println!("R={}, G={}, B={}", red.0, red.1, red.2);
}

常见用法是newtype模式——用元组结构体包装已有类型来获得类型安全:

struct Meters(f64);
struct Seconds(f64);

// Meters和Seconds是不同类型,不能混用
fn speed(distance: Meters, time: Seconds) -> f64 {
    distance.0 / time.0
}

enum枚举

Rust的枚举比C/Java的枚举强大得多——每个变体可以携带数据:

enum IpAddr {
    V4(u8, u8, u8, u8),
    V6(String),
}

enum Message {
    Quit,                       // 没有数据
    Move { x: i32, y: i32 },   // 匿名结构体
    Write(String),              // 包含String
    Color(u8, u8, u8),          // 包含三个u8
}

impl Message {
    fn call(&self) {
        match self {
            Message::Quit => println!("Quit"),
            Message::Move { x, y } => println!("Move to ({}, {})", x, y),
            Message::Write(text) => println!("Write: {}", text),
            Message::Color(r, g, b) => println!("Color: #{:02x}{:02x}{:02x}", r, g, b),
        }
    }
}

fn main() {
    let msgs = vec![
        Message::Move { x: 10, y: 20 },
        Message::Write(String::from("hello")),
        Message::Color(255, 128, 0),
        Message::Quit,
    ];
    for msg in &msgs {
        msg.call();
    }
}

模式匹配 (match)

match必须穷尽所有可能:

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(UsState),
}

fn value_in_cents(coin: &Coin) -> u32 {
    match coin {
        Coin::Penny => {
            println!("Lucky penny!");
            1
        }
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter(state) => {
            println!("Quarter from {:?}", state);
            25
        }
    }
}

_通配符匹配所有剩余情况:

fn describe_number(n: i32) -> &'static str {
    match n {
        0 => "zero",
        1..=9 => "single digit",
        10..=99 => "double digits",
        _ => "large number",
    }
}

Option和Result

Rust没有null,用Option<T>表示值可能不存在:

fn find_first_a(s: &str) -> Option<usize> {
    s.find('a')
}

fn main() {
    let text = "Rust language";
    
    match find_first_a(text) {
        Some(index) => println!("Found 'a' at index {}", index),
        None => println!("No 'a' found"),
    }
    
    // if let:只关心一种情况时更简洁
    if let Some(i) = find_first_a(text) {
        println!("Index: {}", i);
    }
    
    // unwrap_or:提供默认值
    let idx = find_first_a("xyz").unwrap_or(0);
    
    // map:转换内部值
    let doubled = Some(21).map(|x| x * 2); // Some(42)
}

Result<T, E>表示操作可能失败:

use std::fs;
use std::io;
use std::num::ParseIntError;

fn read_number_from_file(path: &str) -> Result<i32, String> {
    let content = fs::read_to_string(path)
        .map_err(|e| format!("Failed to read file: {}", e))?;
    
    let number: i32 = content.trim().parse()
        .map_err(|e: ParseIntError| format!("Failed to parse: {}", e))?;
    
    Ok(number)
}

fn main() {
    match read_number_from_file("number.txt") {
        Ok(n) => println!("Number: {}", n),
        Err(e) => eprintln!("Error: {}", e),
    }
}

?操作符是错误传播的语法糖——如果Result是Err,提前返回错误;如果是Ok,解包值继续执行。

struct和enum是Rust类型系统的基石。struct用于组合相关数据,enum用于表达"多选一"的类型。掌握它们加上match模式匹配,就能表达大部分业务逻辑了。