Error in Best Practices

I was looking for Rust documentation, trying to follow a simple esoteric example for my own educational advantage more than practical. By doing this, I cannot imagine how Rust error handling will be used.

The programming example that I use is to write a function that runs a command in a shell. From the result of the command, I want to get stdout (like String or &str ) and find out if the command worked.

The std::process::Command structure gives me the methods I want, but it seems that the only way to combine them is clumsy and inconvenient:

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

In particular, the nested matching blocks inside run_cmd seem really inconvenient, and the nested matching blocks in main even worse.

What I really would like to do is use a more general error class than FromUtf8Error or io::Error , which I can easily convert from a specific type, but it does not display in this way, I had to use a crude CmdError as something like type associations .

I'm sure there is an easier way to make this more idiomatic, but I have not found it from the documentation I have read so far.

Any pointers appreciated.

+8
rust
source share
1 answer

Defining such things is currently not particularly neat; There are a few things you need to configure with a custom error type, but after you have done this, it will be much easier for you.

First of all, you'll want to implement std::error::Error for a CmdError (which requires std::fmt::Display and std::fmt::Debug ), and then to try! could work automatically, std::convert::From<std::string::FromUtf8Error> and std::convert::From<std::io::Error> , Here are the implementations of these:

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

(The description method in the Error implementation may return a string that is not based on a completed error, for example, β€œthe command could not be started.” If you need details, they will still be in Error.cause() .)

After the implementation of this batch, everything is much simpler, because we can use try! . run_cmd can be written like this:

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

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

Your main may also be somewhat simpler, and the err branch does not need further coordination if you do not care about a specific error; since it now implements fmt::Display , you can just use it directly.

By the way, in the format string {:} should be written as {} ; : is redundant if you do not follow anything. ( {:?} will work for Debug output, but you should use Display if it is facing the user.)

+12
source share

All Articles