处理最佳做法时出错

zja*_*tha 11 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 Code Online (Sandbox Code Playgroud)

特别是,嵌套的匹配块run_cmd看起来很尴尬,嵌套的匹配块main更糟糕.

我真正想做的是能够使用一个更普通的错误类,FromUtf8Error或者io::Error我可以从任何具体类型轻松转换成类型,但它似乎没有出现类型系统以这种方式设计,所以我不得不使用原油CmdError作为联合类型.

我确信有一个更简单的方法来做这个更惯用,但我还没有从我到目前为止阅读的文档中找到它.

任何指针赞赏.

Chr*_*gan 14

定义这样的东西目前不是特别好的东西; 您需要使用自定义错误类型设置一些内容,但在完成后,事情会轻松得多.

首先,你将要执行std::error::ErrorCmdError(这需要std::fmt::Displaystd::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),
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

实施该批次后,事情变得更加容易,因为我们可以使用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)))
}
Run Code Online (Sandbox Code Playgroud)

因为try!使用From基础设施,这一切都简单得多; 第一行可以返回Err(CmdError::IoError(_))(用于Command.output()返回Result<_, io::Error>),第二行可以返回Err(CmdError::UtfError(_))(用于String::from_utf8(…)返回Result<_, FromUtf8Error>).

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

顺便说一下,在格式字符串中,{:}应该写成{}; :如果没有任何东西,这是多余的.({:?}可用于显示Debug输出,但Display如果它面向用户,您应该更喜欢使用.)