4、Rust基础速通(二):结构体与枚举、模式匹配、错误处理(Result/Option)、常用集合(Vec/HashMap)
好,咱们接着往下走。上一章我们把 Rust 的变量、函数、所有权这些基本功过了一遍。这一章,咱们要啃几块硬骨头——结构体、枚举、模式匹配,还有错误处理和常用集合。
说实话,这些才是 Rust 真正“干活”的东西。我在做量化回测引擎的时候,每天跟它们打交道。你想想看,没有结构体你怎么组织订单数据?没有枚举你怎么表示市场状态?没有 Result 你怎么处理 API 调用失败?
嗯,咱们一个一个来。
4.1 结构体:把数据捆在一起
结构体说白了就是把你关心的数据打包成一个整体。比如一个订单,有价格、数量、方向、时间戳——你总不能每次都用四个变量传来传去吧?
struct Order {
price: f64,
quantity: u32,
side: String, // "buy" 或 "sell"
timestamp: u64,
}
用起来也很直接:
let order = Order {
price: 100.5,
quantity: 1000,
side: String::from("buy"),
timestamp: 1699000000,
};
println!("价格: {}", order.price);
pub 但不可变,状态用 pub(crate) 加 mut。这样代码一读就明白哪些是核心数据,哪些是运行时状态。
结构体还可以有方法。注意,方法是绑定到结构体上的函数,用 impl 块定义:
impl Order {
fn is_buy(&self) -> bool {
self.side == "buy"
}
fn value(&self) -> f64 {
self.price * self.quantity as f64
}
}
println!("是否买入: {}", order.is_buy());
这里有个细节:&self 是借用,不会拿走所有权。如果你要修改结构体,就用 &mut self。
4.2 枚举:让状态更清晰
枚举在 Rust 里比 C 语言强大得多。它不只是列举几个值,每个变体还能携带数据。
我在做交易系统时,用枚举表示订单状态:
enum OrderStatus {
Pending,
PartiallyFilled { filled_qty: u32, avg_price: f64 },
Filled { avg_price: f64 },
Cancelled,
Rejected(String), // 带拒绝原因
}
你看,PartiallyFilled 和 Filled 还带着成交数据。这在 C 语言里你得搞个联合体,一不小心就踩坑。Rust 枚举自带类型安全,编译器帮你检查。
4.3 模式匹配:Rust 的瑞士军刀
模式匹配是 Rust 最爽的特性之一。配合枚举用,简直无敌。
fn handle_order(status: OrderStatus) {
match status {
OrderStatus::Pending => println!("等待处理"),
OrderStatus::PartiallyFilled { filled_qty, avg_price } => {
println!("已成交 {} 股,均价 {}", filled_qty, avg_price);
}
OrderStatus::Filled { avg_price } => {
println!("全部成交,均价 {}", avg_price);
}
OrderStatus::Cancelled => println!("已取消"),
OrderStatus::Rejected(reason) => println!("被拒: {}", reason),
}
}
模式匹配还能解构结构体、元组,甚至嵌套匹配。比如:
let point = (3, 5);
match point {
(0, 0) => println!("原点"),
(x, 0) => println!("X 轴上,x={}", x),
(0, y) => println!("Y 轴上,y={}", y),
(x, y) => println!("普通点 ({}, {})", x, y),
}
你想想看,这比一堆 if-else 清晰多少?
4.4 错误处理:Result 和 Option
Rust 没有异常。它用两个枚举来处理“可能出错”和“可能为空”的情况。
Option:处理“可能有值”
比如从 HashMap 里取一个 key,可能不存在:
let mut prices = std::collections::HashMap::new();
prices.insert("AAPL", 150.0);
let price = prices.get("AAPL"); // 返回 Option<&f64>
match price {
Some(p) => println!("AAPL 价格: {}", p),
None => println!("找不到 AAPL"),
}
我个人习惯用 unwrap_or 或 unwrap_or_else 给个默认值:
let price = prices.get("GOOG").copied().unwrap_or(0.0);
Result:处理“可能失败”
Result 有两个变体:Ok(T) 和 Err(E)。比如读取文件:
use std::fs::File;
use std::io::Read;
fn read_config(path: &str) -> Result<String, std::io::Error> {
let mut file = File::open(path)?; // ? 运算符:出错就提前返回
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
}
? 运算符只能在返回 Result 或 Option 的函数里用。我刚开始写 Rust 时老忘记这个,编译器报错才反应过来。嗯,多写几次就习惯了。
调用时:
match read_config("config.toml") {
Ok(content) => println!("配置内容: {}", content),
Err(e) => eprintln!("读取失败: {}", e),
}
4.5 常用集合:Vec 和 HashMap
这两个是量化开发里最常用的集合。咱们重点说。
Vec:动态数组
Vec 就是可增长的数组。存价格序列、订单列表,都用它。
let mut prices: Vec<f64> = Vec::new();
prices.push(100.5);
prices.push(101.2);
prices.push(99.8);
// 或者用宏
let mut prices = vec![100.5, 101.2, 99.8];
// 遍历
for price in &prices {
println!("{}", price);
}
// 索引访问(注意边界检查)
if let Some(p) = prices.get(0) {
println!("第一个价格: {}", p);
}
我在回测引擎里经常用 windows 方法做滑动窗口:
let prices = vec![100.0, 101.0, 102.0, 103.0, 104.0];
let sma: Vec<f64> = prices.windows(3)
.map(|w| w.iter().sum::<f64>() / 3.0)
.collect();
// sma = [101.0, 102.0, 103.0]
HashMap:键值对
存股票代码到价格的映射,或者订单 ID 到订单对象的映射,都用 HashMap。
use std::collections::HashMap;
let mut portfolio = HashMap::new();
portfolio.insert("AAPL", 100);
portfolio.insert("GOOG", 50);
// 更新
portfolio.entry("AAPL").and_modify(|e| *e += 10).or_insert(0);
// 遍历
for (ticker, shares) in &portfolio {
println!("持有 {}: {} 股", ticker, shares);
}
HashMap::with_capacity(n) 分配空间,避免频繁 rehash。我在处理百万级订单簿时,这个优化能省下不少时间。
4.6 本章知识体系
下面这张图帮你理清本章的核心脉络:
说白了,这一章就是 Rust 的“三板斧”:用结构体和枚举把数据组织好,用模式匹配把逻辑写清楚,用 Result/Option 把错误管住,最后用 Vec 和 HashMap 把数据存起来。量化框架里 80% 的代码都离不开这些东西。
我个人建议你多写几个小例子练手。比如写一个简单的订单簿,用 Vec 存买单和卖单,用枚举表示订单类型,用 Result 处理非法价格。写一遍比看十遍管用。
好,这一章就到这儿。记住:结构体是骨架,枚举是状态,模式匹配是逻辑,错误处理是安全网,集合是工具箱。把这几个玩熟了,Rust 的大门就真正向你敞开了。