rust


Error handling best-practices


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.
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.
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);
}
}
}
In particular, the nested match blocks inside run_cmd seem really awkward, and the nested match blocks in main are even worse.
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.
Any pointers appreciated.
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.
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),
}
}
}
(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().)
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)))
}
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>).
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.
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.)

Related Links

PI constant is ambiguous
Calling an impl method from another impl method
compilation of openssl-sys fails with `openssl/hmac.h: No such file or directory`
Indexing a String
Importing mio::tcp::TcpStream but get std::net::tcp::TcpStream
It is possible to always have Cargo show warnings?
Copy files to the target directory after build
Is it ok to return in main?
Why is this trait/implementation incompatible - bound lifetime vs concrete lifetime
Cannot move out of `req` because it is borrowed
remove duplicates from vector of custom struct
error with % operator inside closure
How can I open a file with the standard text editor?
How to use multiple variables in routes with Nickel?
sdl2-sys won't compile - could not exec the linker: No such file or directory
Cannot borrow as mutable more than once at a time in one code - but can in another very similar

Categories

HOME
protocol-buffers
list
lambda
activex
ant
out-of-memory
android-emulator
lvm
vue-resource
u-boot
tfs2010
cqrs
android-externalstorage
xbap
freertos
ex
async-await
word2vec
salt-cloud
scheduled-tasks
conda
substring
software-packaging
react-dnd
opentext
uri
annotation-processing
source-insight
om-next
char-pointer
des
runtimeexception
cordys-opentext
djcelery
forms-authentication
cartopy
netapp
newtons-method
vungle-ads
spooler
reverse-dns
nomad
realstudio
cyanogenmod
aws-kinesis-firehose
sony-future-lab-n
uiautomatorviewer
gestures
datediff
nivo-slider
mpmovieplayercontroller
ogg
researchkit
nclam
msg
livecycle
fiber
sharepoint-apps
django-redis
opencyc
lowercase
nastran
ngcordova
subfolder
nhibernate-criteria
symja
configurable-product
visual-studio-6
xhprof
air-native-extension
visual-c++-2005
colon
apache-shindig
non-ascii-characters
iirf
sql-view
bcdedit
chefspec
itunes-sdk
smartfox
system-requirements
first-class
liveconnect
mkannotation
type-safety
asio
server-variables
firefly-mv
git-log
formal-semantics
eaccelerator
.net-services
gtktextview
comment-conventions
account-management
parentid

Resources

Mobile Apps Dev
Database Users
javascript
java
csharp
php
android
MS Developer
developer works
python
ios
c
html
jquery
RDBMS discuss
Cloud Virtualization
Database Dev&Adm
javascript
java
csharp
php
python
android
jquery
ruby
ios
html
Mobile App
Mobile App
Mobile App