【问题标题】:Why does Rust implicitly convert between Error types when using '?' but not in return value?为什么 Rust 在使用 '?' 时会在错误类型之间隐式转换?但不是返回值?
【发布时间】:2021-02-23 18:43:35
【问题描述】:

给出以下两种错误类型和函数来说明它们的用法(Rust Playground link):

#[derive(std::fmt::Debug)]
struct MyError;

#[derive(std::fmt::Debug)]
struct OtherError;

impl std::error::Error for MyError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        None
    }
}

impl std::fmt::Display for MyError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "MyError")
    }
}

impl std::fmt::Display for OtherError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "OtherError")
    }
}

impl std::error::Error for OtherError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        None
    }
}

impl From<OtherError> for MyError {
    fn from(_: OtherError) -> Self {
        MyError {}
    }
}

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

fn other_error() -> Result<(), OtherError> { Ok(()) }

如果我在一个返回 ResultMyError 作为其 Error 类型的函数中,我可以调用返回 MyErrorOtherError 的两个函数,因为它们之间存在一个 From 转换。

但是,我不能简单地为“其他”类型返回 Result,我需要使用 ? 后跟 Ok(())。这在我看来不一致。

例如,这很好用:

fn main() -> Result<(), MyError> {
    my_error()
}

这也可以:

fn main() -> Result<(), MyError> {
    my_error()?;
    other_error()?;
    my_error()
}

但这失败了:

fn main() -> Result<(), MyError> {
    my_error()?;
    other_error()
}

错误:

error[E0308]: mismatched types
  --> src/main.rs:43:5
   |
41 | fn main() -> Result<(), MyError> {
   |              ------------------- expected `std::result::Result<(), MyError>` because of return type
42 |     my_error()?;
43 |     other_error()
   |     ^^^^^^^^^^^^^ expected struct `MyError`, found struct `OtherError`
   |
   = note: expected enum `std::result::Result<_, MyError>`
              found enum `std::result::Result<_, OtherError>`

这是为什么呢?

这使我的一些代码更加冗长,因为我发现我需要这样做才能让它工作:

fn main() -> Result<(), MyError> {
    my_error()?;
    other_error()?;
    Ok(())
}

这是唯一的解决方案吗?我更有兴趣了解它以这种方式工作的原因,但如果我在做一些愚蠢的事情,请随时指出可以做得更好的地方。

【问题讨论】:

  • 我知道这不是您要问的。但简而言之,这是因为? 是糖,其中包括使用From 转换错误。如果只是单独做returning,那会有点可怕
  • 等效的匹配表达式似乎没有调用into() 或其他调用From 的方法?它是否说它在某处这样做?
  • 如果有的话,这个问题让我比@vallentin 之前更加困惑。根据那个答案,? 不应该像我的示例那样工作。
  • Rust Book 似乎没有提到任何关于From:from(err) 的内容,而您链接到的其他答案也没有。最好有一个“正确”的答案来显示try! 的实际作用(该答案中未显示)。只有该答案中提到的 RFC 似乎显示了 e.into() 的用法,但这是一个设计提案(看起来与当前的 Rust 不同)。 IMO 的情况非常不理想。
  • @vallentin 当然可以,但是在您开始同意我的观点之前,答案必须是多么间接,这并没有解决我的问题(并且 RFC 与当前的 Rust 实现不同)? try! 文档提到 try!发生错误时使用 From 执行转换,并且 ? 运算符“替换”它...

标签: error-handling rust


【解决方案1】:

? 运算符等价于try! 宏,其简化版本如下:

macro_rules! r#try {
    ($expr:expr $(,)?) => {
        match $expr {
            Ok(val) => val,
            Err(err) => {
                return Err(From::from(err));
            }
        }
    };
}

您可以在本书的 Recoverable Errors with Result 页面的 A Shortcut for Propagating Errors: the ? Operator 部分中找到相关参考。

清单 9-6 中的 match 表达式的作用与 ? 运算符的作用之间存在差异:调用了 ? 运算符的错误值通过 from函数,定义在标准库的From trait 中,用于将错误从一种类型转换为另一种类型。当?操作符调用from函数时,接收到的错误类型被转换为当前函数的返回类型中定义的错误类型。当函数返回一种错误类型来表示函数可能失败的所有方式时,这很有用,即使部分可能由于许多不同的原因而失败。只要每个错误类型都实现了from 函数来定义如何将自己转换为返回的错误类型,? 运算符就会自动处理转换。

——A Shortcut for Propagating Errors: the ? Operator - The Rust Programming Language

(强调我的)

您还可以在 RFC 243 中找到相关参考,您可以在 Exception type upcasting 部分找到相关信息。

? 运算符因此应该执行这样的隐式转换,本质上是子类型到超类型的强制转换。目前的 RFC 为此目的使用了std::convert::Into 特征(它有一个从From 转发的毯子impl)。 [...]

Exception type upcasting - RFC 243

另见:

Rust By Example 中的Other uses of ? 页面也提到了隐式转换行为。它还提供了创建自定义错误类型的示例。


总结如下:

fn main() -> Result<(), MyError> {
    my_error()?;
    other_error()?;
    Ok(())
}

本质上等同于:

fn main() -> Result<(), MyError> {
    match my_error() {
        Ok(_) => {}
        Err(err) => return Err(From::from(err));
    }
    match other_error() {
        Ok(_) => {}
        Err(err) => return Err(From::from(err));
    }
    Ok(())
}

如果返回 other_error() 并让它隐式转换错误,那会有点吓人。好像例如返回类型被修改了,return ... 隐含地只是Into,那么它可能会引入一些不是很明显的问题。


如果你想避免额外的Ok(()),那么你可以使用map_err(),即.map_err(From::from)。但是,如果它没有立即返回,那么您很容易遇到编译器无法推断出正确类型的情况。

在这些情况下,您可以使用更明确的形式,即.map_err::&lt;MyError, _&gt;(From::from),或者只是.map_err(MyError::from)

fn main() -> Result<(), MyError> {
    my_error()?;
    // other_error().map_err(From::from)
    // other_error().map_err::<MyError, _>(From::from)?;
    other_error().map_err(MyError::from)
}

如果您的MyErrorenum,它有一个MyError::OtherError 变体,那么您甚至可以只使用.map_err(MyError::OtherError)。 这是因为,不仅构造一个enum 看起来像一个函数调用it is actually implemented as functions

【讨论】:

  • 为了完整起见,您可以通过使用 other_error().map_err(From::from) 来绕过额外的 Ok(()),这与 match 语句中的转换操作基本相同。但这并没有那么冗长。
  • @Johann150 确实如此,但要更清楚。如果编译器可以推断出正确的类型,那么该技巧 可以使用From::from,您很快就会遇到问题。在这种情况下,最好改用.map_err(MyError::from)。 (无论如何,谢谢,我已经更新了答案以包含它)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-07
  • 2020-07-11
  • 2013-06-30
相关资源
最近更新 更多