处理最佳做法时出错

时间:2015-03-07 05:46:48

标签: rust

我一直在摸索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赞赏。

2 个答案:

答案 0 :(得分:14)

目前定义这样的事情并不是特别干净的事情;您需要使用自定义错误类型设置一些内容,但在完成后,事情会变得更加容易。

首先,您需要为std::error::Error(需要CmdErrorstd::fmt::Display)实施std::fmt::Debug,然后try!才能std::convert::From<std::string::FromUtf8Error>自动工作,std::convert::From<std::io::Error>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), } } } 。以下是这些的实现:

description

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

实施该批次后,事情变得更加容易,因为我们可以使用try!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<_, io::Error>返回Err(CmdError::UtfError(_))),第二行可以返回String::from_utf8(…)Result<_, FromUtf8Error>返回main })。

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

顺便说一句,在格式字符串中,{}应写为:;如果没有任何事情,{:?}是多余的。 (Debug可用于显示Display输出,但如果面向用户,则应优先使用{{1}}。)

答案 1 :(得分:1)

如何在2019年做

问号运算符(?

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

容易的错误类型定义

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

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

结合以上两点,问题中给出的示例可以用非常简洁和可读的方式重写:

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)
    }
}