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
The basic question is: if an implementation of the Iterator trait returns a Result<T, E>, what should the iterator do after an error is returned from next() that makes it impossible to continue iterating?
Some context:
As a learning project, I'm attempting to implement a parsing library to parse STUN messages, as defined in RFC5389 in Rust. STUN is a binary network protocol, and as such I would be parsing byte slices. Specifically, the protocol specifies that zero or more dynamically-sized attributes can be encoded into the message. Thus, I am trying to construct an iterator which can be used to iterate over the bytes, yielding subslices for each attribute.
Thus, I have something like this...
pub struct StunAttributeIterator<'a> {
data: &'a [u8],
}
impl<'a> Iterator for StunAttributeIterator<'a> {
type Item = Result<StunAttribute<'a>, StunAttributeError>;
fn next(&mut self) -> Option<Self::Item> {
if self.data.len() == 0 {
return None;
}
// Ensure there are enough bytes left in the stream to parse the next attribute.
if self.data.len() < ATTRIBUTE_TYPE_LENGTH_BYTES {
return Some(Err(StunAttributeError::UnexpectedEndOfStream));
}
// Parse the data and get a slice of the dynamic bytes to return
let data = ...;
// Modify the iterator to have the slice move to the start of the next attribute
self.data = ...;
return Some(Ok(StunAttribute { data }));
}
}
There are a number of things that could go wrong here, and I've included an example in the if statement. If something goes wrong, it's a good indication that the byte stream being parsed is malformed, and thus there is no reason to continue attempting to parse.
On the one hand, I could just leave the code as-is, but I worry that this could create some infinite loops; if the error is ignored next() could be continuously called returning an Err each time. On the other, I could change the iterator so that on a subsequent call to next() after the error None is returned.
Are there guidelines/best practices for what to do as the implementer of the iterator this situation? I know some iterator adapters are aware of iterators that return Result<T, E>, but probably not all.
Let's look at what the docs say:
Returns None when iteration is finished. Individual iterator implementations may choose to resume iteration, and so calling next() again may or may not eventually start returning Some(Item) again at some point.
So as far as the Iterator contract is concerned, you can return anything from next() at any time -- even returning Some after previously returning None.
(The FusedIterator tag indicates that the iterator's next() promises to only return None after it has returned None before.)
All that to say, there isn't a required behavior. You're not even talking about None here, you're talking about Some(Err(_)), so even if the contract of FusedIterator wasn't a separate thing and was instead mandated by Iterator, you'd still technically be fine here no matter what you choose to do.
There are a few Iterator utilities that can interact specifically with a sequence of Results. For example, you can .collect() an iterator of Result<T, E> into a Result<Vec<T>, E>, but this assumes that you only want to look at the produced sequence if no errors occurred whatsoever.
I would argue that there are a few sensible things you could do after yielding an error:
If the failure is permanent, yield None afterwards. The following next() call could restart the operation, if you would like. (Or, return None forever, and then it would be a good idea to implement FusedIterator to communicate this.)
If the failure is temporary (the operation can be retried) then you could try it again, and leave it up to the caller to figure out when they want to stop.
Either of the prior options depending on either configuration or a method call. For example, you could have a retries argument to your iterator's constructor/factory indicating how many sequential failures to return before giving up and returning None, or you could add a fn retry() to your type. If next() returns an error, it would return None on the following call unless retry() was called immediately prior, in which case it would retry the operation.
Whatever, you decide, document it clearly. The second case has the potential to result in a never-ending sequence of errors, which makes not retrying the operation the safest thing to do.
Having said that, I have written a similar type of iterator and I had it retry in the case of an error. Some of my callers use ? on each element to bail on an error, others implement retry logic that will eventually bail if too many errors happen.
Context: I am learning Rust & WebAssembly and as a practice exercise I have a project that paints stuff in HTML Canvas from Rust code. I want to get the query string from the web request and from there the code can decide which drawing function to call.
I wrote this function to just return the query string with the leading ? removed:
fn decode_request(window: web_sys::Window) -> std::string::String {
let document = window.document().expect("no global window exist");
let location = document.location().expect("no location exists");
let raw_search = location.search().expect("no search exists");
let search_str = raw_search.trim_start_matches("?");
format!("{}", search_str)
}
It does work, but it seems amazingly verbose given how much simpler it would be in some of the other languages I have used.
Is there an easier way to do this? Or is the verbosity just the price you pay for safety in Rust and I should just get used to it?
Edit per answer from #IInspectable:
I tried the chaining approach and I get an error of:
temporary value dropped while borrowed
creates a temporary which is freed while still in use
note: consider using a `let` binding to create a longer lived value rustc(E0716)
It would be nice to understand that better; I am still getting the niceties of ownership through my head. Is now:
fn decode_request(window: Window) -> std::string::String {
let location = window.location();
let search_str = location.search().expect("no search exists");
let search_str = search_str.trim_start_matches('?');
search_str.to_owned()
}
which is certainly an improvement.
This question is really about API design rather than its effects on the implementation. The implementation turned out to be fairly verbose mostly due to the contract chosen: Either produce a value, or die. There's nothing inherently wrong with this contract. A client calling into this function will never observe invalid data, so this is perfectly safe.
This may not be the best option for library code, though. Library code usually lacks context, and cannot make a good call on whether any given error condition is fatal or not. That's a question client code is in a far better position to answer.
Before moving on to explore alternatives, let's rewrite the original code in a more compact fashion, by chaining the calls together, without explicitly assigning each result to a variable:
fn decode_request(window: web_sys::Window) -> std::string::String {
window
.location()
.search().expect("no search exists")
.trim_start_matches('?')
.to_owned()
}
I'm not familiar with the web_sys crate, so there is a bit of guesswork involved. Namely, the assumption, that window.location() returns the same value as the document()'s location(). Apart from chaining calls, the code presented employs two more changes:
trim_start_matches() is passed a character literal in place of a string literal. This produces optimal code without relying on the compiler's optimizer to figure out, that a string of length 1 is attempting to search for a single character.
The return value is constructed by calling to_owned(). The format! macro adds overhead, and eventually calls to_string(). While that would exhibit the same behavior in this case, using the semantically more accurate to_owned() function helps you catch errors at compile time (e.g. if you accidentally returned 42.to_string()).
Alternatives
A more natural way to implement this function is to have it return either a value representing the query string, or no value at all. Rust provides the Option type to conveniently model this:
fn decode_request(window: web_sys::Window) -> Option<String> {
match window
.location()
.search() {
Ok(s) => Some(s.trim_start_matches('?').to_owned()),
_ => None,
}
}
This allows a client of the function to make decisions, depending on whether the function returns Some(s) or None. This maps all error conditions into a None value.
If it is desirable to convey the reason for failure back to the caller, the decode_request function can choose to return a Result value instead, e.g. Result<String, wasm_bindgen::JsValue>. In doing so, an implementation can take advantage of the ? operator, to propagate errors to the caller in a compact way:
fn decode_request(window: web_sys::Window) -> Result<String, wasm_bindgen::JsValue> {
Ok(window
.location()
.search()?
.trim_start_matches('?')
.to_owned())
}
I am doing a for_each loop over a stream of futures received via a mspc::Receiver
rx.for_each(move |trade| {
if something_true {
continue;
}
// down here I have computation logic which returns a future
});
I would like to do something like the logic above.
Of course, I could just do an if/else statement but both branches have to return the same type of future, which is hard for me to do as the future I generate in my computation logic is a long chain of messy futures. Which got me thinking if there is actually a simple way of approaching this, like a continue or some sort?
Let's solve the two issues separately. First, the easiest: if your chain of futures inside for_each() is not homogeneous (they rarely will be), consider returning a boxed future (i.e. Box<dyn Future<Item = _, Error = _>>). You may need to typecast the closure return to that, as the compiler will sometimes not get what you are trying to do.
Now, for the "continue if condition" - this typically means you're filtering out certain elements of the stream, which indicates that the better function to call may include filter() or an intermediate state - i.e. returning a future whose item type is Option<_>, and then filtering based on that in the next member of the chain.
No, you cannot. continue is syntax that is only accepted by the core Rust language and crates cannot make use of it.
You could instead return early:
rx.for_each(move |trade| {
if true {
return future::ok(());
}
future::ok(())
});
both branches have to return the same type of future
Use Either or a boxed trait object
rx.for_each(move |trade| {
if true {
return Either::A(future::ok(()));
}
Either::B(future::lazy(|| future::ok(())))
});
See also:
How do I conditionally return different types of futures?
I'd probably move the condition to the stream such that the for_each never sees it:
rx.filter(|trade| true)
.for_each(move |trade| future::ok(()));
When it is known that some piece of code might throw an error, we make use of try/catch blocks to ignore such errors and proceed. This is done when the error is not that important but maybe we only want to log it:
try{
int i = 1/0;
} catch( ArithmeticException e){
System.out.println("Encountered an error but would proceed.");
}
x = y;
Such a construct in Java would continue on to execute x = y;.
Can I make use of match to do this or any other construct?
I do see a try! macro, but perhaps it would return in case of an error with the return type of the method as Result.
I want to use such a construct in a UT to ensure it continues to run even after an error has occurred.
Functions in Rust which can fail return a Result:
Result<T, E> is the type used for returning and propagating errors. It is an enum with the variants, Ok(T), representing success and containing a value, and Err(E), representing error and containing an error value.
I highly recommend reading the Error Handling section in the Rust Book:
Rust has a number of features for handling situations in which something goes wrong
If you want to ignore an error, you have different possibilities:
Don't use the Result:
let _ = failing_function();
The function will be called, but the result will be ignored. If you omit let _ = , you will get a warning. As of Rust 1.59, you can omit the let and just write _ = failing_function();.
Ignore the Err variant of Result using if let or match:
if let Ok(ret) = failing_function() {
// use the returned value
}
You may also convert the Result into Option with Result::ok:
let opt = failing_function().ok();
Unwrap the error. This code panics if an error occurred though:
let ret = failing_function().unwrap();
// or
let ret = failing_function().expect("A panic message to be displayed");
try!() unwraps a result and early returns the function, if an error occurred. However, you should use ? instead of try! as this is deprecated.
See also:
What is this question mark operator about?
Is the question mark operator ? equivalent to the try! macro?
How to do error handling in Rust and what are the common pitfalls?
Just skimming through the Rust guide (guessing game), this code fragment doesn't seem right to me:
let num = match input_num {
Some(num) => num,
None => {
println!("Please input a number!");
continue;
}
};
How does type inference of num work in this scenario? The first match case obviously return a number, whereas the second match case is just println & continue statement, which doesn't return anything(or return ()). How does the compiler assume it's type safe?
Let's look at that piece of code more closely:
loop {
// ... some code omitted ...
let num = match input_num {
Some(num) => num,
None => {
println!("Please input a number!");
continue;
}
};
// ... some code omitted ...
}
The match statement is located inside a loop, and there are several constructs in the language which help control the looping process. break exits from a loop early, while continue skips the rest of the code in the loop and goes back to its beginning (restarts it). So this match above basically can be read basically as "Check the number, and if it is there, assign it to num variable, otherwise output a message and restart from the beginning".
The behavior of "otherwise" branch is important: it ends with a control transfer operation, continue in this case. The compiler sees continue and knows that the loop is going to be restarted. Consequently, it does not really matter what value this branch yields, because it will never be used! It may as well never yield anything.
Such behavior often is modeled with so-called bottom type, which is a subtype of any type and which does not have values at all. Rust does not have subtyping (essentially), so such type is deeply magical. It is denoted as ! in type signatures:
fn always_panic() -> ! {
panic!("oops!")
}
This function always panics, which causes stack unwinding and eventual termination of the thread it was called in, so its return value, if there was one, will never be read or otherwise inspected, so it is absolutely safe not to return anything at all, even if it is used in expression context which expects some concrete type:
let value: int = always_panic();
Because always_panic() has return type !, the compiler knows that it is not going to return anything (in this case because always_panic() starts stack unwinding), it is safe to allow it to be used in place of any type - after all, the value, even if it was there, is never going to be used.
continue works exactly in the same way, but locally. None branch "returns" type !, but Some branch returns value of some concrete numeric type, so the whole match statement is of this numeric type, because the compiler knows that None branch will lead to control transfer, and its result, even if it had one, will never be used.
continue is, along with break and return, "divergent". That is, the compiler knows that control flow does not resume after it, it goes somewhere else. This is also true of any function which returns !; this is how the compiler knows that functions like std::rt::begin_unwind never return.