Table of contents
Open Table of contents
它做什么
? 放在 Result 或 Option 后面,意思是:成功就解包继续,失败就提前返回。
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 — 所以 ? 只能用在返回 Result 或 Option 的函数里。
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 做错误类型转换