【问题标题】:Error handling best-practices错误处理最佳实践
【发布时间】:2015-03-07 05:46:48
【问题描述】:

我一直在摸索 Rust 的文档,试图执行一个简单的深奥示例,而不是为了我自己的教育利益而不是实用性。这样做时,我似乎无法理解如何使用 Rust 的错误处理。

我正在使用的编程示例是编写一个在 shell 中运行命令的函数。从命令的结果中我想检索stdout(作为String&str)并知道命令是否失败。

std::process::Command 结构为我提供了我想要的方法,但似乎将它们组合起来的唯一方法是笨拙和笨拙:

use std::process::Command;
use std::string::{String, FromUtf8Error};
use std::io::Error;


enum CmdError {
    UtfError(FromUtf8Error),
    IoError(Error),
}


// I would really like to use std::error::Error instead of CmdError,
// but the compiler complains about using a trait in this context.
fn run_cmd(cmd: &str) -> Result<String, CmdError> {
    let cmd_result = Command::new("sh").arg("-c").arg(cmd).output();

    match cmd_result {
        Err(e) => {
            return Err(CmdError::IoError(e));
        }
        Ok(v) => {
            let out_result = String::from_utf8(v.stdout);

            match out_result {
                Err(e) => {
                    return Err(CmdError::UtfError(e));
                }
                Ok(v) => {
                    return Ok(v);
                }
            }
        }
    }
}


fn main() {
    let r = run_cmd("echo 'Hello World!'");

    match r {
        Err(e) => {
            match e {
                CmdError::IoError(e) => {
                    panic!("Failed to run command {:}", e);
                }
                CmdError::UtfError(e) => {
                    panic!("Failed to run command {:}", e);
                }
            }
        }
        Ok(e) => {
            print!("{:}", e);
        }
    }
}

尤其是run_cmd里面的嵌套匹配块看起来真的很别扭,main里面的嵌套匹配块就更糟了。

我真正想做的是能够使用比FromUtf8Errorio::Error 更通用的错误类别,我可以轻松地从任一具体类型转换成类型,但它不会出现类型系统是这样设计的,所以我不得不使用粗略的CmdError 来代替union type

我确信有一种更简单的方法可以做到这一点,而且更惯用,但到目前为止我还没有从我读过的文档中找到它。

感谢任何pointers

【问题讨论】:

    标签: rust


    【解决方案1】:

    目前定义这样的东西并不是一件特别整洁的事情;您需要使用自定义错误类型进行一些设置,但完成之后,事情会变得容易得多。

    首先,您需要为CmdError 实现std::error::Error(这需要std::fmt::Displaystd::fmt::Debug),然后为了使try! 可以自动工作,std::convert::From&lt;std::string::FromUtf8Error&gt;std::convert::From&lt;std::io::Error&gt; .以下是它们的实现:

    use std::error::Error;
    use std::string::FromUtf8Error;
    use std::fmt;
    use std::io;
    
    #[derive(Debug)]
    enum CmdError {
        UtfError(FromUtf8Error),
        IoError(io::Error),
    }
    
    impl From<FromUtf8Error> for CmdError {
        fn from(err: FromUtf8Error) -> CmdError {
            CmdError::UtfError(err)
        }
    }
    
    impl From<io::Error> for CmdError {
        fn from(err: io::Error) -> CmdError {
            CmdError::IoError(err)
        }
    }
    
    impl Error for CmdError {
        fn description(&self) -> &str {
            match *self {
                CmdError::UtfError(ref err) => err.description(),
                CmdError::IoError(ref err) => err.description(),
            }
        }
    
        fn cause(&self) -> Option<&Error> {
            Some(match *self {
                CmdError::UtfError(ref err) => err as &Error,
                CmdError::IoError(ref err) => err as &Error,
            })
        }
    }
    
    impl fmt::Display for CmdError {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            match *self {
                CmdError::UtfError(ref err) => fmt::Display::fmt(err, f),
                CmdError::IoError(ref err) => fmt::Display::fmt(err, f),
            }
        }
    }
    

    (Error 实现中的description 方法可能会返回一个不基于包装错误的字符串,例如“无法运行命令”。如果想要详细信息,它们仍然会在@987654332 中@.)

    在实现了很多之后,事情就容易多了,因为我们可以使用try!run_cmd 可以这样写:

    fn run_cmd(cmd: &str) -> Result<String, CmdError> {
        let output = try!(Command::new("sh").arg("-c").arg(cmd).output());
        Ok(try!(String::from_utf8(output.stdout)))
    }
    

    因为try! 使用From 基础架构,这一切都简单多了;第一行可能返回Err(CmdError::IoError(_))Command.output() 返回Result&lt;_, io::Error&gt;),第二行可能返回Err(CmdError::UtfError(_))String::from_utf8(…) 返回Result&lt;_, FromUtf8Error&gt;)。

    你的main 也可以稍微简单一些,如果你不关心特定的错误,err 分支不需要任何进一步的匹配;因为它现在实现了fmt::Display,所以你可以直接使用它。

    顺便说一句,在格式字符串中,{:} 应该写成{}: 如果后面不跟任何东西,那就是多余的。 ({:?} 可用于显示 Debug 输出,但如果是面向用户的,您应该更喜欢使用 Display。)

    【讨论】:

      【解决方案2】:

      2019年怎么办

      问号运算符 (?)

      Rust 现在有问号运算符,可以轻松传播错误。您可以在 Rust 书籍中非常完整的 chapter about recoverable errors 中了解它。

      简单的错误类型定义

      crates.io 上有几个 crate,可以轻松定义自定义错误类型,而无需编写之前必须编写的所有样板。 声明零开销错误类型的一种非常简单的方法是使用custom_error crate。我是那个箱子的作者。

      示例:解析外部命令的输出

      结合以上两点,问题中给出的例子可以改写成非常简洁易读的方式:

      use std::string::FromUtf8Error;
      use std::io;
      use std::process::Command;
      use custom_error::custom_error;
      
      custom_error! {CmdError
          UtfError{source: FromUtf8Error} = "The command returned an invalid string: {}",
          IoError{source: io::Error} = "Unable to launch command: {}"
      }
      
      fn run_cmd(cmd: &str) -> Result<String, CmdError> {
          let out_bytes = Command::new("sh").arg("-c").arg(cmd).output()?.stdout;
          let out_string = String::from_utf8(out_bytes)?;
          Ok(out_string)
      }
      
      fn main() {
          match run_cmd("echo 'Hello World'") {
              Ok(res) => println!("{}", res),
              Err(e)  => eprintln!("{}", e)
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2018-09-12
        • 2017-11-26
        • 2016-06-06
        • 1970-01-01
        • 1970-01-01
        • 2014-11-28
        • 2019-10-28
        • 2011-03-26
        • 1970-01-01
        相关资源
        最近更新 更多