错误处理最佳实践 [英] Error handling best-practices

查看:69
本文介绍了错误处理最佳实践的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I've been fumbling through Rust's documentation trying to execute a simple esoteric example for my own educational benefit more than practicality. While doing this, I can't seem to wrap my head around how Rust's error handling is meant to be used.

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

The programming example I'm using is to write a function that runs a command in a shell. From the result of the command I want to retrieve stdout (as a String or &str) and know whether or not the command failed.

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

The std::process::Command struct gives me the methods I want, but it seems that the only way to combine them is kludgy and awkward:

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里面嵌套的match块看起来真的很别扭,而main里面的嵌套match块更糟糕.

In particular, the nested match blocks inside run_cmd seem really awkward, and the nested match blocks in main are even worse.

我真正想做的是能够使用比 FromUtf8Errorio::Error 更通用的错误类别,我可以轻松地将其转换为从任一具体类型,但它似乎没有以这种方式设计类型系统,所以我不得不使用粗略的 CmdError 作为某种 联合类型.

What I'd really like to do is be able to use a more general class of error than FromUtf8Error or io::Error which I can type convert into easily from either concrete type, but it doesn't appear the type system is designed in this way, so I had to use the crude CmdError as somewhat of a union type instead.

我确信有一种更简单的方法可以做到这一点,而且更符合地道,但我目前还没有从我阅读过的文档中找到它.

I'm sure there's an easier way to do this which is more idiomatic, but I haven't found it from the documentation I've read so far.

感谢任何指针.

推荐答案

像这样定义目前并不是一件特别整洁的事情;对于自定义错误类型,您需要进行一些设置,但完成这些设置后,事情就容易多了.

Defining things like this is not a particularly neat thing at present; there are a few things you need to set up with your custom error type, but after you’ve done that things are a lot easier.

首先,您需要为 CmdError 实现 std::error::Error(这需要 std::fmt::Displaycode> 和 std::fmt::Debug),然后为了 try! 可以自动工作,std::convert::From<std::string::FromUtf8Error>std::convert::From.以下是它们的实现:

First of all, you will want to implement std::error::Error for CmdError (which requires std::fmt::Display and std::fmt::Debug), and then in order that try! can work automatically, std::convert::From<std::string::FromUtf8Error> and std::convert::From<std::io::Error>. Here are the implementations of those:

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 方法可能会返回一个不基于包装错误的字符串,例如无法运行命令".如果需要详细信息,它们仍然会出现在 Error.cause() 中.)

(The description method in the Error implementation could possibly return a string not based on the wrapped error, e.g. "failed to run command". If one wants the details, they’ll still be there in Error.cause().)

在实现了那么多之后,事情就容易多了,因为我们可以使用 <代码>尝试!.run_cmd 可以这样写:

After implementing that lot, things are a lot easier because we can use try!. run_cmd can be written thus:

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(…) 返回 结果<_, FromUtf8Error>).

Because try! uses the From infrastructure, this is all a lot simpler; the first line may return an Err(CmdError::IoError(_)) (for Command.output() returns Result<_, io::Error>), and the second line may return an Err(CmdError::UtfError(_)) (for String::from_utf8(…) returns Result<_, FromUtf8Error>).

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

Your main can also be somewhat simpler then, with the err branch not needing any further matching if you don’t care about the particular error; as it implements fmt::Display now, you can just use it directly.

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

Incidentally, in a format string, {:} should be written as {}; the : is superfluous if not followed by anything. ({:?} would work for showing Debug output, but you should prefer to use Display if it’s user-facing.)

这篇关于错误处理最佳实践的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆