跳转到正文
zeno's blog
返回

Rust 基础:? 操作符

专题: Rust 基础

Table of contents

Open Table of contents

它做什么

? 放在 ResultOption 后面,意思是:成功就解包继续,失败就提前返回

let player = db.query("SELECT ...").await?;
//                                       ^
//  Ok(player)  → 解包,拿到 player,继续往下走
//  Err(e)      → 立即 return Err(e.into()),函数到此结束

没有 ? 的世界

async fn get_player(pool: &PgPool, id: i32) -> Result<Player, AppError> {
    let row = match sqlx::query("SELECT * FROM players WHERE id = $1")
        .bind(id)
        .fetch_one(pool)
        .await
    {
        Ok(row) => row,
        Err(e) => return Err(e.into()),  // 手动提前返回
    };

    let token = match generate_token(&row) {
        Ok(t) => t,
        Err(e) => return Err(e.into()),  // 又来一遍
    };

    let session = match cache_session(&token).await {
        Ok(s) => s,
        Err(e) => return Err(e.into()),  // 再来一遍
    };

    Ok(session)
}

每次可能失败的地方都要写一遍 match Ok/Err,大量重复。

? 的世界

async fn get_player(pool: &PgPool, id: i32) -> Result<Player, AppError> {
    let row = sqlx::query("SELECT * FROM players WHERE id = $1")
        .bind(id)
        .fetch_one(pool)
        .await?;            // 失败就 return Err

    let token = generate_token(&row)?;      // 失败就 return Err

    let session = cache_session(&token).await?;  // 失败就 return Err

    Ok(session)
}

逻辑完全一样,但代码只保留了”正常路径”,错误处理全部由 ? 隐式完成。

? 的展开规则

expression?

编译器把它展开成(概念上):

match expression {
    Ok(val) => val,
    Err(e) => return Err(From::from(e)),  // 注意这里调了 From::from
}

两个关键点:

1. 它会 return — 所以 ? 只能用在返回 ResultOption 的函数里。

fn main() {
    let f = File::open("config.toml")?;  // 编译错误!main 返回 (),不是 Result
}

// 改成这样就行
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let f = File::open("config.toml")?;  // OK
    Ok(())
}

2. 它会自动做类型转换(From::from — 所以不同类型的错误可以混用。

自动错误转换

async fn login(pool: &PgPool, redis: &redis::Client) -> Result<String, AppError> {
    let player = sqlx::query_as::<_, Player>("...")
        .fetch_one(pool)
        .await?;         // sqlx::Error → 通过 From 转成 AppError

    let mut conn = redis.get_multiplexed_async_connection().await?;
                         // redis::RedisError → 通过 From 转成 AppError

    let token = jwt::encode(&claims, &key)?;
                         // jwt::errors::Error → 通过 From 转成 AppError

    Ok(token)
}

三个 ? 产生三种不同的错误类型,但函数返回的是 Result<_, AppError>? 自动调用 From::from 把具体错误转成 AppError——前提是你实现了 From trait:

enum AppError {
    Database(sqlx::Error),
    Redis(redis::RedisError),
    Auth(jwt::errors::Error),
}

impl From<sqlx::Error> for AppError {
    fn from(e: sqlx::Error) -> Self {
        AppError::Database(e)
    }
}

impl From<redis::RedisError> for AppError {
    fn from(e: redis::RedisError) -> Self {
        AppError::Redis(e)
    }
}

impl From<jwt::errors::Error> for AppError {
    fn from(e: jwt::errors::Error) -> Self {
        AppError::Auth(e)
    }
}

手写 From 太烦了。实际项目用 thiserror 自动生成:

use thiserror::Error;

#[derive(Error, Debug)]
enum AppError {
    #[error("database error: {0}")]
    Database(#[from] sqlx::Error),       // 自动生成 From<sqlx::Error>

    #[error("redis error: {0}")]
    Redis(#[from] redis::RedisError),    // 自动生成 From<redis::RedisError>

    #[error("auth error: {0}")]
    Auth(#[from] jwt::errors::Error),    // 自动生成 From<jwt::errors::Error>
}

? 用在 Option 上

fn get_username(player: &Option<Player>) -> Option<String> {
    let player = player.as_ref()?;  // None → return None, Some → 解包
    let name = player.profile.as_ref()?.name.clone();
    Some(name)
}

// 等价于
fn get_username(player: &Option<Player>) -> Option<String> {
    match player.as_ref() {
        Some(p) => match p.profile.as_ref() {
            Some(profile) => Some(profile.name.clone()),
            None => None,
        },
        None => None,
    }
}

不能混用 Result 和 Option

fn foo() -> Result<String, Error> {
    let val = some_option?;  // 编译错误!Option 的 ? 需要返回 Option,不是 Result
}

// 解决方案:用 .ok_or() 把 Option 转成 Result
fn foo() -> Result<String, Error> {
    let val = some_option.ok_or(Error::NotFound)?;  // OK
}

链式 ?

// 可以直接链在方法调用后面
let name = db.query("SELECT name FROM players WHERE id = $1")
    .bind(id)
    .fetch_one(pool)
    .await?           // 数据库查询可能失败
    .get::<String, _>("name");

// 多层嵌套也没问题
let config = std::fs::read_to_string("config.toml")?;
let parsed: Config = toml::from_str(&config)?;
let db_url = parsed.database.url.ok_or(AppError::MissingConfig("db url"))?;
let pool = PgPool::connect(&db_url).await?;

总结

?  =  "成功就继续,失败就带着错误提前返回"

三条规则:
1. 只能在返回 Result 或 Option 的函数里用
2. 失败时自动 return(不是 panic,是正常的错误传播)
3. 自动调用 From::from 做错误类型转换

分享这篇文章:

上一篇
Rust 基础:Serde 序列化框架
下一篇
Rust 基础:宏系统