Alternative to `unwrap()` when `T` does not implement `Debug` - rust

I am aware that x.unwrap() when x: Result<T, E> does not work when E does not implement Debug: unwrap() would need to print out the Err variant in case x.is_err() but it cannot. Sometimes, however, especially in tests, I do need to get my hands on the Ok value. I assumed x.expect() would do the trick, as I am the one that specifies the message upon failure. And yet, for some reason I don't completely understand, expect(), too, requires E: Debug. This means that I always end up taking the verbose, repetitive way:
let x_ok = match x {
Ok(x_ok) => x_ok,
Err(_) => panic!("Something went horribly wrong!"),
}
I cannot imagine there wouldn't be a more standardised solution to this problem, and yet I struggle to find one. How does one quickly get_ok_or_panic if the Err type of a Result does not implement Debug?

Idiomatic code would just forward the error:
fn do_it() -> Result<(), E> {
let x_ok = x_ok?;
// work with x_ok
Ok(())
}
or handle the error gracefully where possible:
let x_ok = x_ok.unwrap_or(sane_default);
If neither is an option and you absolutely have to panic you can use let … else:
let Ok(x_ok) = x_ok else { panic!("Something went horribly wrong") };

The typical workaround is to use unwrap_or_else():
let x_ok = x.unwrap_or_else(|_| panic!("Something went horribly wrong!"));

Related

Result and Option unwrapping

I am trying to learn Rust and I would like to understand the conceptual fundamentals. In Rust, we often use Result<T, E> as a return type. Basically, a type, which consists of - Ok() and Err(), and it is up to the caller to handle these.
But what I am kind a surprised is fact, that both Ok() and Err(), have again their "options" -> Namely Some and None.
Example code:
fn integer_divide(a: u32, b: u32) -> Result<u32, String> {
if b != 0 {
Ok(a / b)
} else {
Err("Division by zero!".into())
}
}
let result = integer_divide(5, 0);
if(result.is_err()){
if(result.err().is_some()){
// some logic
}
}
Question
So basically, we need to double check before we get to resulting value of every function (First for Err or Ok, then for Some or None)? If yes, I find this very clunky. Especially in case my integer_divide function, where reliably I am able to say, it can never have a result with Err() of None value.
It would make more sense to me, if we would just unwrap Err(), then check if it's value is None or some other type... Especially in case, where I am 100% sure, it cannot hold None value. Any thoughts appreciated.
Note
I am aware of existence of ? operator. I just want to understand concept.
Considering result.err() alone, nothing ensures that result actually contains an error, thus this method cannot return an error every time we invoke it.
The usual way in Rust to provide something which is optional is to return an Option, hence the is_some() method.
This does not mean that the Result contains such an Option; this Option is created by the err() method just in case result did not contain an error.
You know that there is actually an error inside result because you tested just before with result.is_err(); but the designer of the err() method did not know, a long time ago, that you would test is_err() just before calling err().
The usual way to handle result without double check would be
match result {
Ok(v) => {
println!("result is {}", v);
}
Err(e) => {
println!("error: {}", e);
}
}
is_err() serves to answer the question "does this result contain an error?". If that's all you're interested in, then you're good to go:
if result.is_err() {
// logic here
}
Result is just an enum with two well-defined variants, so if you also need to access the value of the error, you can use if let to match the Err variant:
if let Err(e) = result {
// logic here, `e` contains the error value
}
Finally, if you need to handle both Ok and Err in the same go, use match, as shown in the answer by #prog-fh.

How to mimic varargs for utility functions?

Result.expect()'s console output wasn't what I needed, so I extended Result with my own version:
trait ResultExt<T> {
fn or_exit(self, message: &str) -> T;
}
impl<T> ResultExt<T> for ::std::result::Result<T, Error> {
fn or_exit(self, message: &str) -> T {
if self.is_err() {
io::stderr().write(format!("FATAL: {} ({})\n", message, self.err().unwrap()).as_bytes()).unwrap();
process::exit(1);
}
return self.unwrap();
}
}
As I understand, Rust doesn't support varargs yet, so I have to use it like that, correct?
something().or_exit(&format!("Ah-ha! An error! {}", "blah"));
That's too verbose compared to either Java, Kotlin or C. What is the preferred way to solve this?
I don't think the API you suggested is particularly unergonomic. If maximum performance matters, it might make sense to put the error generation in a closure or provide an API for that too, so the String is only allocated when there is actually an error, which might be especially relevant when something is particularly expensive to format. (Like all the _else methods for std::result::Result.)
However, you might be able to make it more ergonomic by defining a macro which takes a result, a &str and format parameters. This could look like this for example: (This is based on #E_net4's comment)
macro_rules! or_exit {
($res:expr, $fmt:expr, $($arg:tt)+) => {
$res.unwrap_or_else(|e| {
let message = format!($fmt, $($arg)+);
eprintln!("FATAL: {} ({})\n", message, e);
process::exit(1)
})
};
}
fn main() {
let x: Result<i32, &'static str> = Err("dumb user, please replace");
let _ = or_exit!(x, "Ah-ha! An error! {}", "blahh");
}
Rust Playground
Note this might not yield the best error messages if users supply invalid arguments, I did not want to change your code too much, but if you decide to actually have the macro only be sugar and nothing else you should probably extend your API to take a closure instead of a string. You might want also to reconsider the naming of the macro.

Is it possible to implement a method that takes a format string?

I've found myself using .expect("...") to panic with an helpful error message in a lot of places where I don't care about recovering from an error. Example:
let p = "foo.txt";
let f = File::open(p).expect("File not found");
However, I would like to print more information by using a formatted string. This is what I could do:
let f = File::open(p).expect(&format("{} not found", p));
This has two problems:
The format call will be evaluated eagerly.
It's unnecessarily verbose.
Ideally, I would like to write:
// pseudocode
let f = File::open(p).expect_fmt("{} not found", p);
But I suppose that's not possible without variadic generic functions and compile-time parsing of strings.
The only alternative I've found is the following:
let f = File::open(p).unwrap_or_else(|_| panic!("{} not found", p));
Which is still a little bit too verbose for my taste.
It's acceptable if the answer uses nightly Rust.
TL;DR: No.
While there's not much to be done about reducing your verbosity issues, you can avoid the memory allocation by using std::fmt::Arguments
trait ResultExt<T, E> {
fn expect_fmt<D>(self, msg: D) -> T
where
D: std::fmt::Display;
}
impl<T, E> ResultExt<T, E> for Result<T, E>
where
E: std::error::Error,
{
fn expect_fmt<D>(self, msg: D) -> T
where
D: std::fmt::Display,
{
match self {
Ok(t) => t,
Err(e) => panic!("{}: {}", msg, e),
}
}
}
use std::fs::File;
fn main() {
let p = "foo.txt";
let f = File::open(p).expect_fmt(format_args!("{} not found", p));
}
Feel free to adjust the trait bounds as you see fit.
not possible without variadic generic functions and compile-time parsing of strings
That's exactly what the format_args macro does, but you still have to call it.
For avoiding eagerly formatting of the error message a closure may be used, toghether with a simple macro for reducing verbosity a little bit:
macro_rules! crash {
( $( $p:tt ),* ) => {
|_| panic!($($p),*);
};
}
then the statement sugarizes as:
let f = File::open(p).unwrap_or_else(crash!("{} not found", p));
No much added value with this approach.
Have you considered error-chain or failure as alternatives for your error management design?
They have a lot to offer for effective error handling.
Using a simple macro, you should anyway tweak creatively the syntax to get near your desiderata, below an error-chain based example:
macro_rules! or {
( $( $p:tt ),* ) => {{
|| format!($($p),*)
}};
}
fn run() -> Result<()> {
use std::fs::File;
// This operation will fail
let p = "foo";
File::open(p).chain_err(or!("unable to open {} file", p))?;
Ok(())
}
Read as: invoke chain_error method with a variadic arg list.

What is this question mark operator about?

I'm reading the documentation for File:
//..
let mut file = File::create("foo.txt")?;
//..
What is the ? in this line? I do not recall seeing it in the Rust Book before.
As you may have noticed, Rust does not have exceptions. It has panics, but their use for error-handling is discouraged (they are meant for unrecoverable errors).
In Rust, error handling uses Result. A typical example would be:
fn halves_if_even(i: i32) -> Result<i32, Error> {
if i % 2 == 0 {
Ok(i / 2)
} else {
Err(/* something */)
}
}
fn do_the_thing(i: i32) -> Result<i32, Error> {
let i = match halves_if_even(i) {
Ok(i) => i,
Err(e) => return Err(e),
};
// use `i`
}
This is great because:
when writing the code you cannot accidentally forget to deal with the error,
when reading the code you can immediately see that there is a potential for error right here.
It's less than ideal, however, in that it is very verbose. This is where the question mark operator ? comes in.
The above can be rewritten as:
fn do_the_thing(i: i32) -> Result<i32, Error> {
let i = halves_if_even(i)?;
// use `i`
}
which is much more concise.
What ? does here is equivalent to the match statement above with an addition. In short:
It unpacks the Result if OK
It returns the error if not, calling From::from on the error value to potentially convert it to another type.
It's a bit magic, but error handling needs some magic to cut down the boilerplate, and unlike exceptions it is immediately visible which function calls may or may not error out: those that are adorned with ?.
One example of the magic is that this also works for Option:
// Assume
// fn halves_if_even(i: i32) -> Option<i32>
fn do_the_thing(i: i32) -> Option<i32> {
let i = halves_if_even(i)?;
// use `i`
}
The ? operator, stabilized in Rust version 1.13.0 is powered by the (unstable) Try trait.
See also:
Is the question mark operator ? equivalent to the try! macro?
Why do try!() and ? not compile when used in a function that doesn't return Option or Result?
It is a postfix operator that unwraps Result<T, E> and Option<T> values.
If applied to Result<T, E>, it unwraps the result and gives you the inner value, propagating the error to the calling function.
let number = "42".parse::<i32>()?;
println!("{:?}", number); // 42
When applied to an Option<T>, it propagates None to the caller, leaving you the content of the Some branch to deal with.
let val = Some(42)?;
println!("{:?}", val); // 42
The ? operator can only be used in a function that returns Result or Option like so:
use std::num::ParseIntError;
fn main() -> Result<(), ParseIntError> {
let number = "42".parse::<i32>()?;
println!("{:?}", number);
Ok(())
}
It is a convenience offered by Rust, that eliminates boilerplate code and makes function's implementation simpler.
It is used for propagating errors. Sometimes we write code that might fail but we do not want to catch and handle error immediately. Your code will not be readable if you have too much code to handle the error in every place. Instead, if an error occurs, we might want to let our caller deal with it. We want errors to propagate up the call stack.
// file type is Result if "?" is not used
// file:Result<File,Error>
let mut file = File::create("foo.txt");
// file type is File if "?" is used
// file:File
let mut file = File::create("foo.txt")?;
// if an error occurs, code after this line will not be executed
// function will return the error
The behavior of ? depends on whether this function returns a successful result or an error result:
If it is a success, it unwraps the Result to get the success value inside. Value is assigned to the variable file
If the result is an error, the error is NOT assigned to the variable file. Error is returned by the function to the caller
Using ? same as this code
let mut file = match File::create("foo.txt") {
Err(why) => panic!("couldn't create {}: {}", display, why),
Ok(file) => file,
};
? also works similarly with the Option type. In a function that returns Option, you
can use ? to unwrap a value and return early in the case of None :
The existing answers are all great! I would like to give a small code snippet to demo the use of From::from() behand this question mark:
fn _parse(str: &str) -> Result<i32, &str> {
if let Ok(num) = str.parse::<i32>() {
Ok(num)
} else {
Err(str)
}
}
fn parse(str: &str) -> Result<(), String> {
let num = _parse(str)?;
println!("{}", num);
Ok(())
}
The use of ? in function parse() can be manually rewritten as:
fn parse(str: &str) -> Result<(), String> {
match _parse(str) {
Ok(n) => {
println!("{}", n);
Ok(())
}
Err(str) => Err(<String as From<&str>>::from(str)),
}
}

What is unwrap in Rust, and what is it used for?

I have this code that uses .unwrap():
fn main() {
let paths = std::fs::read_dir("/home/user").unwrap();
for path in paths {
println!("Name: {}", path.unwrap().path().display());
}
}
After looking at the definition of unwrap,
pub fn unwrap(self) -> T {
match self {
Ok(t) => t,
Err(e) => unwrap_failed("called `Result::unwrap()` on an `Err` value", e),
}
}
And the signature of read_dir
pub fn read_dir<P: AsRef<Path>>(path: P) -> io::Result<ReadDir>
Am I correct in understanding that unwrap returns the T type that is passed in Result?
In Rust, when you have an operation that may either return a T or fail, you will have a value of type Result<T,E> or Option<T> (E will be the error condition in case of an interesting error).
The function unwrap(self) -> T will give you the embedded T if there is one. If instead there is not a T but an E or None then it will panic.
It is best used when you are positively sure that you don't have an error. If that is not the case usually it is better either pattern-match the error or use the try! macro ? operator to forward the error.
In your example, the call to read_dir() returns a io::Result<ReadDir> because opening the directory might fail. And iterating the opened directory returns multiple values of type io::Result<DirEntry> because reading the directory might also fail.
With try! ? it would be something like this:
fn try_main() -> std::io::Result<()> {
let entries = std::fs::read_dir("/home/user")?;
for entry in entries {
println!("Name: {}", entry?.path().display());
}
Ok(())
}
fn main() {
let res = try_main();
if let Err(e) = res {
println!("Error: {}", e);
}
}
Look how every error case is checked.
(Updated to use ? instead of try!(). The macro still works, but the ? is preferred for new code).
The problem is that reading a line from a file produces a potential error type. The type is
Result<String,std::io::Error>
Result is an enum. There are two potential values in the Result, they are used for error handling and management. The first value is Err. If Err is populated, there was an error in the function that was called. The other potential selection is Ok. Ok contains a value.
enum Result<T, E> {
Ok(T),
Err(E),
}
Enum is a complex type, rather than a single value. To get the value we are looking for, we use unwrap() to unwrap the value.
unwrap() is used here to handle the errors quickly. It can be used on any function that returns Result or Option (Option is also enum). If the function returns an Ok(value), you will get the value. If the function returns an Err(error), the program will panic.

Resources