How to do error handling in Rust and what are the common pitfalls? - error-handling

I noticed that Rust does not have exceptions. How to do error handling in Rust and what are the common pitfalls? Are there ways to control flow with raise, catch, reraise and other stuff? I found inconsistent information on this.

Rust generally solves errors in two ways:
Unrecoverable errors. Once you panic!, that's it. Your program or thread aborts because it encounters something it can't solve and its invariants have been violated. E.g. if you find invalid sequences in what should be a UTF-8 string.
Recoverable errors. Also called failures in some documentation. Instead of panicking, you emit a Option<T> or Result<T, E>. In these cases, you have a choice between a valid value Some(T)/Ok(T) respectively or an invalid value None/Error(E). Generally None serves as a null replacement, showing that the value is missing.
Now comes the hard part. Application.
Unwrap
Sometimes dealing with an Option is a pain in the neck, and you are almost guaranteed to get a value and not an error.
In those cases it's perfectly fine to use unwrap. unwrap turns Some(e) and Ok(e) into e, otherwise it panics. Unwrap is a tool to turn your recoverable errors into unrecoverable.
if x.is_some() {
y = x.unwrap(); // perfectly safe, you just checked x is Some
}
Inside the if-block it's perfectly fine to unwrap since it should never panic because we've already checked that it is Some with x.is_some().
If you're writing a library, using unwrap is discouraged because when it panics the user cannot handle the error. Additionally, a future update may change the invariant. Imagine if the example above had if x.is_some() || always_return_true(). The invariant would changed, and unwrap could panic.
? operator / try! macro
What's the ? operator or the try! macro? A short explanation is that it either returns the value inside an Ok() or prematurely returns error.
Here is a simplified definition of what the operator or macro expand to:
macro_rules! try {
($e:expr) => (match $e {
Ok(val) => val,
Err(err) => return Err(err),
});
}
If you use it like this:
let x = File::create("my_file.txt")?;
let x = try!(File::create("my_file.txt"));
It will convert it into this:
let x = match File::create("my_file.txt") {
Ok(val) => val,
Err(err) => return Err(err),
};
The downside is that your functions now return Result.
Combinators
Option and Result have some convenience methods that allow chaining and dealing with errors in an understandable manner. Methods like and, and_then, or, or_else, ok_or, map_err, etc.
For example, you could have a default value in case your value is botched.
let x: Option<i32> = None;
let guaranteed_value = x.or(Some(3)); //it's Some(3)
Or if you want to turn your Option into a Result.
let x = Some("foo");
assert_eq!(x.ok_or("No value found"), Ok("foo"));
let x: Option<&str> = None;
assert_eq!(x.ok_or("No value found"), Err("No value found"));
This is just a brief skim of things you can do. For more explanation, check out:
http://blog.burntsushi.net/rust-error-handling/
https://doc.rust-lang.org/book/ch09-00-error-handling.html
http://lucumr.pocoo.org/2014/10/16/on-error-handling/

If you need to terminate some independent execution unit (a web request, a video frame processing, a GUI event, a source file to compile) but not all your application in completeness, there is a function std::panic::catch_unwind that invokes a closure, capturing the cause of an unwinding panic if one occurs.
let result = panic::catch_unwind(|| {
panic!("oh no!");
});
assert!(result.is_err());
I would not grant this closure write access to any variables that could outlive it, or any other otherwise global state.
The documentation also says the function also may not be able to catch some kinds of panic.

Related

What is purpose of `unwrap()` if the return value is not used?

I found this Rust code for getting a line from stdin:
use std::io;
fn main() {
let mut line = String::new();
io::stdin().read_line(&mut line).unwrap();
println!("Input: {}", line);
}
io::stdin().read_line(&mut line) sets the line variable to a line read from stdin. From my understanding, read_line() returns a Result value, which can be pattern-matched, or .unwrap() can be used to get the inner value if it is not an Err.
However, the returned value of read_line() is never used. Only the line string variable is used, but people use .unwrap() most of the time even if it is not used.
What is the purpose of unwrap() if the returned value is not used? Just to throw an error?
What is the purpose of unwrap() if the returned value is not used? Just to throw an error?
Yes, but that's not all.
Ignoring potential errors is bad; there's a big difference between an empty line and a line that's not been read because of an error; for example in a typical "pipeline" command in a shell, the program needs to stop when it stops receiving input, otherwise the user has to kill it.
In C, ignoring errors is too easy. Many languages solve this by having exceptions, but Rust doesn't.
In order to avoid the issue plaguing C programs that it's too easy to forget to check the return code, normally Rust functions will bundle the expected return value and error in Result, so that you have to check it to get the return value.
There is one potential issue left, however: what if the caller doesn't care about the return value? Most notably, when the value is (), nobody really cares about it.
There is a bit of compiler magic invoked here: the Result structure is tagged with the #[must_use] attribute. This attribute makes it mandatory to do something with Result when it's returned.
Therefore, in your case, not only is unwrapping good, it's also the simplest way to "do something" and avoid a compilation warning.
If you don't want to "elegantly" handle cases where there is a failure to read a line from stdin (e.g. by attempting it once again or picking a default value), you can use unwrap() to trigger a panic; it silences the warning caused by a Result that is not used:
warning: unused result which must be used
--> src/main.rs:5:5
|
5 | io::stdin().read_line(&mut line);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: #[warn(unused_must_use)] on by default

Alternative to the try (?) operator suited to iterator mapping

In the process of learning Rust, I am getting acquainted with error propagation and the choice between unwrap and the ? operator. After writing some prototype code that only uses unwrap(), I would like to remove unwrap from reusable parts, where panicking on every error is inappropriate.
How would one avoid the use of unwrap in a closure, like in this example?
// todo is VecDeque<PathBuf>
let dir = fs::read_dir(&filename).unwrap();
todo.extend(dir.map(|dirent| dirent.unwrap().path()));
The first unwrap can be easily changed to ?, as long as the containing function returns Result<(), io::Error> or similar. However, the second unwrap, the one in dirent.unwrap().path(), cannot be changed to dirent?.path() because the closure must return a PathBuf, not a Result<PathBuf, io::Error>.
One option is to change extend to an explicit loop:
let dir = fs::read_dir(&filename)?;
for dirent in dir {
todo.push_back(dirent?.path());
}
But that feels wrong - the original extend was elegant and clearly reflected the intention of the code. (It might also have been more efficient than a sequence of push_backs.) How would an experienced Rust developer express error checking in such code?
How would one avoid the use of unwrap in a closure, like in this example?
Well, it really depends on what you wish to do upon failure.
should failure be reported to the user or be silent
if reported, should one failure be reported or all?
if a failure occur, should it interrupt processing?
For example, you could perfectly decide to silently ignore all failures and just skip the entries that fail. In this case, the Iterator::filter_map combined with Result::ok is exactly what you are asking for.
let dir = fs::read_dir(&filename)?;
let todos.extend(dir.filter_map(Result::ok));
The Iterator interface is full of goodies, it's definitely worth perusing when looking for tidier code.
Here is a solution based on filter_map suggested by Matthieu. It calls Result::map_err to ensure the error is "caught" and logged, sending it further to Result::ok and filter_map to remove it from iteration:
fn log_error(e: io::Error) {
eprintln!("{}", e);
}
(|| {
let dir = fs::read_dir(&filename)?;
todo.extend(dir
.filter_map(|res| res.map_err(log_error).ok()))
.map(|dirent| dirent.path()));
})().unwrap_or_else(log_error)

How can I match against a std::io::Error with a Windows error code?

In my tiny little Rust program, I'm calling a Windows API and want to make sure that everything went OK. In order to do so, I'm using the functionality provided by std::io::Error::last_os_error(). I also want to deliberately ignore some of the errors that may occur.
I could not find any information on how to do that, other than just printing out the Error returned by that function. What I actually need is a kind of a match statement in which I can handle the various known errors.
I understand that the function returns an std::io::Error struct but I could not find any information on error IDs or similar concepts.
Currently, my code reads like
use std::io::Error;
fn main() {
// do some stuff that may lead to an error
match Error::last_os_error() {
163 => // Do nothing. This error is to be expected
// _ => Err("Something went wrong "),
}
}
The actual problem is that last_os_error() returns an error struct but I want to match on the ID of the error that is listed in WinError.h (this program only runs under Windows).
Can anybody help me on how to distinguish the various errors behind the error structs in such a match statement?
You can use io::Error::raw_os_error to get the original error code and then match against that:
match Error::last_os_error().raw_os_error() {
Some(163) => {} // Do nothing. This error is to be expected
Some(e) => panic!("Unknown OS error {}", e),
None => panic!("Not an OS error!"),
}
It's a different question of whether this is a good idea or not. You can also match against known error types. I'd recommend using that where possible. You may also want to create (or find) an enum that maps the various error codes to human-readable values, as it's a lot easier to tell that you meant NotEnoughMemory instead of SecurityDescriptorInvalid than it is to tell the difference of 123 and 132.

Fail safe assertions in Swift

I commonly use assertions in Objective-C where I want to assert a value. On a debug build I assert in order to stop execution of the program and check if my assumption was incorrect. However, on production builds I find a way to fail safely in a way to minimise the user impact. I achieve this by creating a macro that encapsulates an NSAssert within an if statement which also executes the code I would like to run as a failsafe on production. For example:
An assertion macro I would use:
#define AssertTrueOrExecute(condition, action) \
if (!condition) { \
NSAssert(testCondition, #"Condition failed"); \
action; \
}
Somewhere in my application I would have something like this:
- (void)someMethod
{
BOOL testCondition = ...
// Ensure the testCondition is true before proceeding any further
AssertTrueOrExecute(testCondition, return);
// Potentially unsafe code that never gets executed if testCondition is false
}
- (void)someReturningMethod
{
BOOL testCondition = ...
// Ensure the testCondition is true before proceeding any further
AssertTrueOrExecute(testCondition, return #"safe string");
// Potentially unsafe code that never gets executed if testCondition is false
}
Since I cannot define a macro like the one mention in Swift, is there a way to have the same behaviour? That is how would I go about having a Swift equivalent for my AssertTrueOrExecute macro?
Update:
To further explain the question, if I was using Swift I currently would write something like this:
func someMethod () {
let testCondition : Bool = ...
// Ensure the testCondition is true before proceeding any further
if (!testCondition) {
assert(testCondition);
return;
}
// Potentially unsafe code that never gets executed if testCondition is false
}
So the question is more along the lines of how can the if statement with the assertions be wrapped in a similar way I have the Objective-C macro so that I can assert or return early for example?
Update 2:
Another example would be in function that returns something, for example:
func someReturningMethod () -> String {
let testCondition : Bool = ...
// Ensure the testCondition is true before proceeding any further
if (!testCondition) {
assert(testCondition);
return "safe string";
}
// Potentially unsafe code that never gets executed if testCondition is false
return "some other string"
}
There are no macros in Swift, but there could be other means in Swift where you could achieve this same functionality as it is possible in Objective-C.
However, the real issue here is, that you try to approach a problem in a way which you really shouldn't:
Do not mix programmer errors and runtime errors!
Instead, make a clear distinction what programmer errors are and what runtime errors are. Handle programmer errors with assertions, and handle runtime errors with NSError respectively in Swift with try & catch and throw.
Note, that the "scope" of a programmer error is not restricted to the point when the program fails through an assertion failure: Very likely such an error has bad side effects which leave the program in an invalid state, and often this assertion detects errors that may have occurred possibly a long time before the assertion failed. So, when an assertion fails, your program is very likely already in an invalid state.
A rule of thumb is, that an assertion failure should not happen in production code (read MUST NOT). Well, these are programmer errors and should be fixed, shouldn't they? You verify your assumptions using assertions in unit tests. If you still fear, that your assumption may break in production and are also sure that this is not a runtime error (which should always be handled gracefully), it should stop the program - all bets are off anyway. In Swift, you can use fatalError for this.
Sometimes, the distinction whether the violation of a certain assumption is a programmer error or whether it's a runtime error is not always that obvious and may depend on the context. As a programmer, though, you can always define what it is. Take a string parameter as example: if you obtain it directly from a text field from a user input who wants to create an account and is asked for his name, you should validate the string and return/throw an error if it doesn't fit your expectation - for example if it is empty, too short etc. That is, in Swift you may throw an error and handle that gracefully on the call-site, possibly in a View Controller. On the other hand, you define that it would not make sense to initialise a User object whose name will be empty. That is, in your init routine you define the precondition that a valid user name must not be empty, and you check this with assert or fatalError. In this scenario your program is correct, when there is no code path which initialises a User whose name is empty.

Reuse the description of an existing Error when creating a new Error

I have the following code in Rust, which does not compile, but shows the intent of what I'd like to do.
pub fn parse(cursor: &mut io::Cursor<&[u8]>) -> io::Result<Ack> {
use self::byteorder::{BigEndian, ReadBytesExt};
use self::core::error::Error;
match cursor.read_u16::<BigEndian>() {
Err(byteorder::Error::Io(error)) => Err(error),
Err(error) =>
Err(io::Error::new(io::ErrorKind::Other, error.description(),
None)),
Ok(value) => Ok(Ack { block_number: value })
}
}
Essentially, I want to take the error description of an error returned by the byteorder library and use it to create the description of an error I'll pass back to the user of my library. This fails with packets.rs:166:58: 166:63 error:errordoes not live long enough, and I understand why.
The byteorder library solves this issue by wrapping an std::io::Result in the byteorder::Error::Io constructor. However, I don't want to take this route because I'd have to define my own error type that wraps either an std::io::Error or a byteorder::Error. It seems to me that my users shouldn't know or care that I use the byteorder library, and it shouldn't be part of my interface.
I'm a Rust newbie and don't yet know the idioms and best practices of the language and design. What are my options for dealing with this?
Your problem is in fact in that io::Error::new()'s second parameter is &'static str, while byteorder::Error::description() returns a &'a str where 'a is lifetime of the error object itself which is less than 'static. Hence you can't use it for io::Error's description.
The simplest fix would be moving byteorder::Error description to detail field of io::Error:
Err(error) =>
Err(io::Error::new(
io::ErrorKind::Other,
"byteorder error",
Some(error.description().to_string())
)),
However, you should seriously consider making a custom wrapper error type which encapsulates all "downstream" errors. With properly written FromError instances you should be able to write something like
try!(cursor.read_u16::<BigEndian>()
.map(|value| Ack { block_number: value }))
instead of your whole match. Custom error wrappers will also help you when your program grows and more "downstream" error sources appear - you could just add new enum variants and/or FromError implementations to support these new errors.
I cannot test your code so I can't be sure. Isn't the ref keyword enough?
Err(byteorder::Error::Io(ref error)) => Err(error),