为什么 '?'运算符使用“发件人”而不是“进人”?

时间:2019-12-28 21:19:47

标签: rust type-conversion

假设有一个外部模块(由第三方库提供):

pub mod external_module {
    #[derive(Debug)]
    pub struct ExternalError;

    pub trait SomeTrait {
        fn do_stuff(&self) -> Result<i32, ExternalError>;
    }
}

我需要在代码中使用SomeTrait。我的代码可能会遇到一些内部错误,因此我定义了自己的错误类型并将其转换为ExternalError

struct MyError;

impl Into<ExternalError> for MyError {
    fn into(self) -> external_module::ExternalError {
        ExternalError {}
    }
}

请注意,由于from<MyError> for ExternalError是在我的名称空间之外定义的(实际上甚至是在我的板条箱之外),因此我无法实现ExternalError

现在,我实现SomeTrait

fn do_my_things() -> Result<(), MyError> {
    Ok(())
}

struct MyStruct;

impl SomeTrait for MyStruct {
    fn do_stuff(&self) -> Result<i32, ExternalError> {
        do_my_things()?;
        Ok(200)
    }
}

Full code in the playground。但是,它将无法编译:

error[E0277]: `?` couldn't convert the error to `external_module::ExternalError`
  --> src/main.rs:28:23
   |
28 |         do_my_things()?;
   |                       ^ the trait `std::convert::From<MyError>` is not implemented for `external_module::ExternalError`
   |
   = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
   = note: required by `std::convert::From::from`

要使其编译,我可以显式使用into()

    do_my_things().map_err(|e| e.into())?;

Playground。这里有两个问题:

  1. 为什么是“?”运算符使用From而不是Into?后者似乎更合理,因为它会更宽松:From<A> for B暗示Into<B> for A,但并非相反。
  2. 在我的特殊情况下处理错误转换的最佳方法是什么?编写.map_err(|e| e.into())看起来很冗长(有时我什至不得不在闭包中明确指定类型,因为编译器无法推断出它们!)

某些情况:遇到这种情况时,我正在玩tonic。该库从.proto个文件生成特征,然后希望您实现它们(请参见the basic helloworld)。如果发生故障,您需要返回Err(tonic::Status),它基本上包含GRPC错误代码和错误消息。我不希望我的内部错误类型与GRPC错误代码有任何关系,我只想在我的错误转换为tonic::Status时添加它们。

1 个答案:

答案 0 :(得分:3)

cursor | V 0 1 2 3 A B C 宏为initally implemented,没有隐式错误转换。该宏是从renamedis_ok!的,后来错误转换为implemented时,他们选择使用try!特性,later被{在更普遍的转化特征之后的{1}}特征是在六个月后implemented

如@mcarton所述,the RFC for the ? operator使用FromError特征进行错误转换。它还说:“ 后缀From<E>运算符可以应用于Into值,并且等效于当前的?宏。”但是,如{ {3}},stbuehler撰写:

  

遗憾的是会有回归的。如果没有实现错误类型的(非平凡的)Result实例,则编译器可以在某些情况下推论该类型,而在使用try!()({{1}的集合时)则无法推论该类型。 }实例受当前板条箱限制,编译板条箱时无法知道完整的Into实例集。

所以我对此的解释是,From<...>宏在IntoFrom特征存在之前添加了错误转换支持,并且因为try!运算符应具有相同的行为作为From宏,并且更改为Into会破坏类型推导,因此?运算符是使用try!实现的。


话虽如此,只要Into是板条箱中的一种类型,就可以实现?

From

this comment