You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为自定义结果枚举实现`?`操作符/`FromResidual` trait,以处理特定错误时的数据库保存逻辑

如何为自定义结果枚举实现?操作符/FromResidual trait,以处理特定错误时的数据库保存逻辑

嘿,我完全懂你的痛点——既要用自定义结果类型区分普通错误和需要持久化的错误,又想让?操作符能像处理标准Result一样自动处理这些逻辑,还不想每次都写繁琐的match容易漏处理。这事儿是能搞定的,我一步步给你拆解:

第一步:让SavableResult支持?操作符

Rust的?操作符本质依赖于std::ops::Try trait,这个 trait 定义了类型如何转换为“成功值”或“剩余错误值”,以及如何从剩余值恢复。我们需要给你的SavableResult实现这个 trait:

首先补全必要的定义(比如你的ApiErr):

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ApiErr {
    pub message: String,
    pub save_on_err: bool,
    // 其他业务字段...
}

#[derive(Copy, PartialEq, Eq, Debug, Clone)]
pub enum SavableResult<T, E> {
    Ok(T),
    Err(E),
    SaveOnErr(E),
}

然后定义一个“剩余值”类型,用来区分普通错误和需要保存的错误:

#[derive(Copy, PartialEq, Eq, Debug, Clone)]
pub enum SavableResidual<E> {
    Err(E),
    SaveOnErr(E),
}

接下来实现Try trait:

use std::ops::Try;

impl<T, E> Try for SavableResult<T, E> {
    // 成功时返回的类型
    type Output = T;
    // 失败时的剩余值类型
    type Residual = SavableResidual<E>;

    // 将SavableResult转换为标准Result,供?处理
    fn into_result(self) -> Result<Self::Output, Self::Residual> {
        match self {
            SavableResult::Ok(val) => Ok(val),
            SavableResult::Err(err) => Err(SavableResidual::Err(err)),
            SavableResult::SaveOnErr(err) => Err(SavableResidual::SaveOnErr(err)),
        }
    }

    // 从剩余值恢复为SavableResult,供?返回给上层函数
    fn from_residual(residual: Self::Residual) -> Self {
        match residual {
            SavableResidual::Err(err) => SavableResult::Err(err),
            SavableResidual::SaveOnErr(err) => SavableResult::SaveOnErr(err),
        }
    }
}

现在你的test2函数里的test3()?就能正常编译运行了!?会自动把SaveOnErr和Err都当作错误返回给上层,上层可以像你写的test1那样通过match处理保存逻辑。

第二步:解决业务场景中的自动保存痛点

你提到的登录失败时需要更新并保存auth实体的场景,直接在trait实现里硬编码保存逻辑不太现实(因为保存需要依赖数据库事务等上下文),但我们可以用通用包装函数来封装这个逻辑,避免重复写match:

首先调整SavableResult,让SaveOnErr携带需要保存的实体:

#[derive(Debug, Clone)]
pub enum SavableResult<T, E, S> {
    Ok(T),
    Err(E),
    SaveOnErr(E, S), // S是要保存的实体类型
}

然后写一个通用的处理函数,自动处理保存逻辑:

async fn handle_savable<T, E, S>(
    result: SavableResult<T, E, S>,
    save_action: impl Fn(S) -> Result<(), E>,
) -> Result<T, E> {
    match result {
        SavableResult::Ok(val) => Ok(val),
        SavableResult::Err(err) => Err(err),
        SavableResult::SaveOnErr(err, entity) => {
            // 执行保存操作
            save_action(entity)?;
            // 保存后返回错误
            Err(err)
        }
    }
}

现在你的execute函数就可以简化成这样:

pub async fn execute(&self) -> Result<JwtTokensPair, ApiErr> {
    let mut auth = AuthRepository::get_by_user_id(user_id, &trx).await?;

    // 假设login现在返回携带auth实体的SavableResult
    let login_result = auth.login(&self.password, self.hash_service);
    
    // 用包装函数自动处理保存
    let tokens = handle_savable(login_result, |auth_to_save| {
        AuthRepository::save(&auth_to_save, &trx).await
    }).await?;

    trx.commit().await?;
    Ok(tokens)
}

这样一来,你就不用每次手动写match判断是否需要保存,也不用担心漏处理这种特殊逻辑了。

额外优化:和标准Result互转

如果需要和标准库的Result类型互相转换,可以实现From trait:

impl<T, E> From<Result<T, E>> for SavableResult<T, E, ()> {
    fn from(result: Result<T, E>) -> Self {
        match result {
            Ok(val) => SavableResult::Ok(val),
            Err(err) => SavableResult::Err(err),
        }
    }
}

这样数据库操作返回的Result可以直接用.into()转换成SavableResult,更方便。

备注:内容来源于stack exchange,提问作者ZiiMakc

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.20 11:58:05