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

How do I modify a value after matching on it?
Drop a Rust void pointer stored in an FFI
Iterate two vectors and the rest of the larger one
How do I implement Clone/Copy for an enum that contains a String?
Modeling embedded hardware in Rust and how to have multiple mutable references cleanly?
Should I borrow or copy my small data types?
Using pointer casting to change the “type” of data in memory [duplicate]
Export function only to module test?
Take slice of certain length known at compile time
Is it possible to deactivate file locking in cargo?
“does not live long enough” error in same function
What ways exist to create containers of several types? [duplicate]
How can I optimize reading a UTF-8 string from a file with a known offset and size?
Create vector of objects implementing a trait in Rust
Using loop variable from “..” loop causes type conversion?
What is RFC 401's coerce_inner useful for?

Categories

HOME
svn
keyboard
focus
redmine
ews
visual-studio-2010
websocket
iptables
bittorrent
haxe
roku
azure-graph-api
nexus3
getopenfilename
sap-fiori
buffer
amazon-elb
jodatime
sonata
pywinauto
pickle
conda
quartz.net
pdf.js
router
uri
sales
javax.persistence
fusion
nic
autodesk-model-derivative
objectmapper
mapnik
pdfminer
spinner
cargo
twilio-php
macports
django-crispy-forms
widestring
export-to-pdf
data-uri
minikube
google-prediction
spooler
web-inspector
oracle-bmcs
xcode8.2
bit-shift
knockout-2.0
subforms
test-data
dtd
dymola
pbx
deviare
mathjs
between
cdo.message
adp
android-syncadapter
xcopy
grinder
asp.net-web-api-helppages
fantom
bootstrap-wysiwyg
phpredis
deque
mathematical-expressions
directorysearcher
adhoc-polymorphism
datagridcomboboxcolumn
visual-studio-6
lumia-imaging-sdk
senchatouch-2.4
xmi
boolean-algebra
pre
rautomation
jquery-transit
ax
apportable
gprof
clrprofiler
clearinterval
getopt-long
word-frequency
prefuse
monocross
uipangesturerecognizer
radscheduler
startupscript
jquery-1.4
grails-validation
servlet-container
solandra
explicit
facebook-fbml
rational-unified-process
3270
stretchblt
comment-conventions

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