【问题标题】:How does the question mark operator convert error types? [duplicate]问号运算符如何转换错误类型? [复制]
【发布时间】:2020-10-14 10:53:47
【问题描述】:

这不能编译:

fn foo() -> std::io::Result<()> {
    todo!()
}

pub fn bar() -> anyhow::Result<()> {
    foo()
}

这也不是:

pub fn bar() -> anyhow::Result<()> {
    foo().into()
}

但这确实:

pub fn bar() -> anyhow::Result<()> {
    Ok(foo()?)
}

? 进行转换背后的机制是什么,有没有比Ok(...?) 更好的方法来进行这种转换?

【问题讨论】:

  • 也许你会喜欢fehler crate
  • 您可能想要转换错误,而不是包含它的结果 - 即 foo().map_err(Into::into) 而不是 foo().into()
  • 完全同意。问题“如何正确处理错误?”和“解释如何 deshugars 这个运算符”一样吗? @Shepmaster
  • “问号运算符如何转换错误类型?”; “转换背后的机制是什么?确实” - 来自重复:“那么问号运算符将能够将任何兼容的错误转换为这些类型之一,因为有各种 Into 和 From 特征实现。”
  • 在冗长的回答中埋藏着一条不经意的评论。 title 是同一个问题,但如果你真的阅读了这个问题,你会发现他在问如何从一种错误类型转换为另一种错误类型,他并没有问? 这样做的机制。

标签: error-handling rust


【解决方案1】:

类似于:

// r?
match r {
   Ok(v)=>v,
   Err(x)=>return Err(std::convert::Into::into(x)),
}

所以你最后的代码是这样的:

pub fn bar() -> anyhow::Result<()> {
  Ok(match foo(){
     Ok(x)=>x,
     Err(x)=>return Err(std::convert::Into::into(x)),
  })
}

或者这个:

pub fn bar() -> anyhow::Result<()> {
  let r: () = match foo(){
     Ok(x)=>x,
     Err(x)=>return Err(std::convert::Into::into(x)),
  };
  Ok(r)
}

UPD

正如@Masklinn 所说,它使用 From 特征而不是 Into 和 Try 特征。 你可以了解更多here。 这很令人惊讶,因为 trait Into 的文档说:

在泛型函数上指定特征边界时,优先使用 Into 而不是 From,以确保也可以使用仅实现 Into 的类型。

最后,结果代码如下所示:

pub fn bar() -> anyhow::Result<()> {
  let r: () = match Try::into_result(foo()){
     Ok(x)=>x,
     Err(e)=>return Try::from_error(From::from(e)),
  };
  Ok(r)
}

【讨论】:

  • FWIW 将 try! 宏解糖为 From 而不是 Into,我不记得确切原因(可能是 Into 的推理问题?)。对于RFC 1859,还有第二个特征涉及around the From call
  • @Masklinn 这让我很惊讶,因为docsPrefer using Into over From when specifying trait bounds on a generic function to ensure that types that only implement Into can be used as well. 但你是对的。我会更新答案。
  • 是的,如果 Into 有效,通常建议使用它,因为 Into 会自动为 From 实现,但它并不总是有效,因此对于宏之类的东西来说风险更大。
【解决方案2】:

? 放置在 Result 值之后。因为传播错误模式在 rust 中很常见,所以它提供了? 作为它们的简写。例如:

use std::io;
use std::io::Read;
use std::fs::File;

pub fn read(filename: &str) -> Result<String, io::Error> {
    let f = File::open(filename); // we are trying to read file, which gets dropped when scope ends

    let mut f = match f { // if everything is OK go on
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut s = String::new();
    match f.read_to_string(&mut s) { // if everything is OK put whatever is file into s
        Ok(_) => Ok(s),
        Err(e) => Err(e), 
    }
}

下面和上面的代码做同样的事情:

use std::io::Read;
use std::fs::File;
use std::io;

pub fn read(filename: &str) -> Result<String, io::Error> {
    let mut f = File::open(filename)?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

您甚至可以链接? 运营商:

use std::io;
use std::fs::File;
use std::io::Read;

pub fn read(filename: &str) -> Result<String, io::Error> {
    let mut s = String::new();
    File::open(filename)?.read_to_string(&mut s)?;
    Ok(s)
}

?match expression 之间存在区别:? 运算符还调用from,它在From 特征中定义,它将一种错误类型转换为另一种错误类型。这意味着当函数返回 std::io::Error 但我们的函数内部出现其他类型的错误时,? 会将其转换为 std::io::Error

【讨论】:

  • 是的,我知道? 是干什么用的。我要求澄清From 位。例如,您可以解释为什么我的foo().into() 版本不起作用(大概我需要做类似foo().map_err(Into::into) 的事情?
  • 我的错,我误解了你的问题。在那种情况下,我还没有答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-02-12
  • 1970-01-01
  • 1970-01-01
  • 2020-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多