agentsclimarketplace

Rust error handling

Skill morning-start/agent-skills/lang/rust/rust-error-handling

Rust 错误处理技能,掌握 Result 和 Option 类型、错误传播、自定义错误类型、panic 机制From its SKILL.md

Install
npx -y skills add morning-start/agent-skills --skill rust-error-handling

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

7.9 KB, ~2.3k tokens by cl100k_base, as published. Nobody here has run it

Rust 错误处理

任务目标

  • 本 Skill 用于:处理程序运行中的错误和异常情况
  • 能力包含:Result 类型、Option 类型、错误传播、自定义错误、panic 处理、panic 恢复
  • 触发条件:处理可恢复错误、处理可选值、遇到 panic 错误

前置准备

  • 完成 rust-core 和 rust-generics
  • 理解 Rust 没有异常的机制

Option 枚举

// 标准库定义
enum Option<T> {
    Some(T),
    None,
}

fn find_user(name: &str) -> Option<&str> {
    if name == "Alice" {
        Some("[email protected]")
    } else {
        None
    }
}

// 使用 match
let result = find_user("Alice");
match result {
    Some(email) => println!("Email: {}", email),
    None => println!("User not found"),
}

// if let 简化
if let Some(email) = find_user("Alice") {
    println!("Email: {}", email);
}

// unwrap_or 提供默认值
let email = find_user("Bob").unwrap_or("no email");

// unwrap_or_else 延迟计算默认值
let email = find_user("Bob").unwrap_or_else(|| {
    println!("User not found, using default");
    "[email protected]"
});

// map 转换内部值
let email = find_user("Alice").map(|e| e.to_uppercase());

// and_then 链式调用
fn get_domain(email: &str) -> Option<&str> {
    email.split('@').nth(1)
}

let domain = find_user("Alice")
    .and_then(get_domain);

// is_some/is_none 检查
if find_user("Alice").is_some() {
    println!("User exists");
}

// expect 提供错误信息(调试用)
let email = find_user("Alice").expect("User should exist");

Result 枚举

// 标准库定义
enum Result<T, E> {
    Ok(T),
    Err(E),
}

use std::num::ParseIntError;

fn parse_number(s: &str) -> Result<i32, ParseIntError> {
    s.parse::<i32>()
}

// 使用 match
match parse_number("42") {
    Ok(n) => println!("Number: {}", n),
    Err(e) => println!("Error: {}", e),
}

// unwrap 传播错误(不推荐)
let n = parse_number("42").unwrap();

// expect 提供错误信息
let n = parse_number("42").expect("Failed to parse number");

// unwrap_or 提供默认值
let n = parse_number("not a number").unwrap_or(0);

// unwrap_or_else 延迟计算
let n = parse_number("not a number").unwrap_or_else(|_| 0);

// is_ok/is_err 检查
if parse_number("42").is_ok() {
    println!("Valid number");
}

错误传播

? 运算符

use std::fs::File;
use std::io;
use std::io::Read;

fn read_username() -> Result<String, io::Error> {
    let mut file = File::open("username.txt")?;
    let mut content = String::new();
    file.read_to_string(&mut content)?;
    Ok(content)
}

// 等价的 match 版本
fn read_username_match() -> Result<String, io::Error> {
    let mut file = match File::open("username.txt") {
        Ok(f) => f,
        Err(e) => return Err(e),
    };
    let mut content = String::new();
    match file.read_to_string(&mut content) {
        Ok(_) => Ok(content),
        Err(e) => Err(e),
    }
}

// 链式调用
fn read_username_chained() -> Result<String, io::Error> {
    let mut content = String::new();
    File::open("username.txt")?.read_to_string(&mut content)?;
    Ok(content)
}

? 与 Option

fn last_char(s: &str) -> Option<char> {
    s.lines().last()?.chars().last()
}

// 等价于
fn last_char_match(s: &str) -> Option<char> {
    if let Some(line) = s.lines().last() {
        if let Some(c) = line.chars().last() {
            return Some(c);
        }
    }
    None
}

? 与 From

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

fn read_config() -> Result<i32, Box<dyn std::error::Error>> {
    let content = File::open("config.txt")?.read_to_string()?;
    let n = content.trim().parse::<i32>()?;
    Ok(n)
}

自定义错误类型

简单错误枚举

use std::fmt;

#[derive(Debug)]
enum AppError {
    NotFound(String),
    InvalidInput(String),
    ParseError(std::num::ParseIntError),
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            AppError::NotFound(s) => write!(f, "Not found: {}", s),
            AppError::InvalidInput(s) => write!(f, "Invalid input: {}", s),
            AppError::ParseError(e) => write!(f, "Parse error: {}", e),
        }
    }
}

impl std::error::Error for AppError {}

fn find_user(name: &str) -> Result<String, AppError> {
    if name.is_empty() {
        return Err(AppError::InvalidInput("Name cannot be empty".to_string()));
    }
    if name == "Unknown" {
        return Err(AppError::NotFound(format!("User '{}' not found", name)));
    }
    Ok(format!("{}@example.com", name))
}

使用 thiserror

// Cargo.toml
// [dependencies]
// thiserror = "1.0"

use thiserror::Error;

#[derive(Error, Debug)]
pub enum ConfigError {
    #[error("File not found: {0}")]
    NotFound(String),

    #[error("Invalid format in {filename}: {message}")]
    InvalidFormat { filename: String, message: String },

    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),

    #[error("Parse error: {0}")]
    Parse(#[from] std::num::ParseIntError),
}

使用 anyhow

// Cargo.toml
// [dependencies]
// anyhow = "1.0"

use anyhow::{Context, Result};

fn read_config(path: &str) -> Result<i32> {
    let content = std::fs::read_to_string(path)
        .with_context(|| format!("Failed to read {}", path))?;
    let n = content.trim().parse::<i32>()
        .context("Failed to parse number")?;
    Ok(n)
}

Panic 处理

panic! 宏

fn main() {
    panic!("这是一个 panic");

    // 带回溯
    panic!("错误发生在: {:?}", std::env::current_dir());
}

数组越界

let v = vec![1, 2, 3];
// v[99];  // panic: index out of bounds

// 安全访问
if let Some(item) = v.get(99) {
    println!("{}", item);
}

unwrap 和 expect

// 不安全的 unwrap
let n = parse_number("not a number").unwrap();  // panic

// 推荐:使用 expect 提供上下文
let n = parse_number("not a number").expect("应该是一个数字");

禁用 panic 处理

// Cargo.toml
[profile.release]
panic = 'abort'  // 减小二进制大小

错误处理策略

原则

场景推荐方式
可恢复错误Result
可选值Option
真正的异常情况panic
库代码自定义错误
应用代码anyhow

组合使用

use std::num::ParseIntError;

fn parse_and_double(s: &str) -> Result<i32, ParseIntError> {
    Ok(s.parse::<i32>()? * 2)
}

fn parse_and_double_safe(s: &str) -> Option<i32> {
    Some(s.parse::<i32>().ok()? * 2)
}

fn main() {
    // 组合使用
    let result: Option<i32> = find_user("Alice")
        .and_then(|email| email.split('@').nth(1))
        .and_then(|domain| domain.parse::<i32>().ok());

    // or_else 处理复杂逻辑
    let n = parse_number("invalid")
        .unwrap_or_else(|_| {
            eprintln!("Using default value");
            0
        });
}

与迭代器组合

let numbers = vec!["1", "2", "three", "4", "5"];

let parsed: Vec<i32> = numbers
    .iter()
    .filter_map(|s| s.parse::<i32>().ok())
    .collect();

println!("{:?}", parsed);  // [1, 2, 4, 5]

资源索引

注意事项

  • Rust 没有异常,使用 Result 处理可恢复错误
  • panic 用于真正的不可恢复错误
  • ? 运算符只能用于 Result 和 Option
  • 库应该返回具体错误类型,应用代码可用 anyhow
  • 避免在库中使用 unwrap() 和 expect()

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 626,194. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.