I have a long chain of Result() returning functions, which are chained using and_then and similar functions. I would like to print an error in case any of the function fail, without using the returned value. I need a sort of if_err function, which can be chained to a result, and call a closure in case of an Error:
foo()
.and_then(|x| bar1(x))
.and_then(|x| bar2(x))
.and_then(|x| bar3(x))
.and_then(|x| bar4(x))
.if_err(|err| { println!("Error: {}", err); });
These are the existing patterns available in the language:
foo()
.and_then(|x| bar1(x))
.and_then(|x| bar2(x))
.and_then(|x| bar3(x))
.and_then(|x| bar4(x))
.unwrap_or_else(|err| { println!("Error: {}", err); });
and:
if let Error(err) = foo()
.and_then(|x| bar1(x))
.and_then(|x| bar2(x))
.and_then(|x| bar3(x))
.and_then(|x| bar4(x)) {
println!("Error: {}", err); });
}
Is there anything better than this?
On nightly, you can use Result::inspect_err():
foo()
.and_then(|x| bar1(x))
.and_then(|x| bar2(x))
.and_then(|x| bar3(x))
.and_then(|x| bar4(x))
.inspect_err(|err| { println!("Error: {}", err); });
If you're on stable, you can polyfill it quite easily:
pub trait ResultExt<T, E> {
fn inspect<F: FnOnce(&T)>(self, f: F) -> Self;
fn inspect_err<F: FnOnce(&E)>(self, f: F) -> Self;
}
impl<T, E> ResultExt<T, E> for Result<T, E> {
fn inspect<F: FnOnce(&T)>(self, f: F) -> Self {
if let Ok(ref v) = self { f(v); }
self
}
fn inspect_err<F: FnOnce(&E)>(self, f: F) -> Self {
if let Err(ref v) = self { f(v); }
self
}
}
This is pretty much a matter of opinion, but I would go one of two ways. The first option is definitely my preference, because it makes it clear we don't care about the result. The second is just a way of "cleaning up" the method chain so the closure braces aren't necessary.
Option 1 (playground):
let result = foo()
.and_then(bar1)
.and_then(bar2)
.and_then(bar3)
.and_then(bar4);
if let Err(err) = result {
println!("Error: {}", err);
}
Option 2 (playground):
foo()
.and_then(bar1)
.and_then(bar2)
.and_then(bar3)
.and_then(bar4)
.err()
.map(|err| println!("Error: {}", err));
As an aside, I personally think Result and Option could do with some kind of peek() method (like the method of the same name in Java's stream API), but for now we have to make do without.
ETA: It looks like this feature slipped into the language while I wasn't looking! Thanks to Chayim for bringing it to my attention in their answer.
Related
I'm totally new to Rust and I'm coming from Golang.
I'm trying to translate this simple Golang function to Rust:
func Retry(attempts int, seconds int, f func() error) (err error) {
for i := 0; ; i++ {
err = f()
if err == nil {
return nil
}
if i > 0 {
println("retried")
}
if i >= (attempts - 1) {
break
}
time.Sleep(time.Duration(seconds) * time.Second)
}
return fmt.Errorf("error after %v attempts", attempts)
}
This function is to call like this:
if err := Retry(3, 5, func() error { // this is an immediate function that calls the other one
err := maybeErroringFunction()
return err
}); err != nil {
println("it doesn't respond after all retries")
}
In Rust I'm trying with the below code, but I'm very disappointed and hungry for notions to make it better by finally learning this beautiful language:
use std::{thread, time::Duration};
pub async fn retry(attempts: u8, delay: u64, f: fn() -> Result<(), ()>) -> Result<(), ()> {
for n in 1..attempts {
if n > 1 {
println!("retried {} times", n);
}
match f() {
Ok(()) => break, // this is wrong here because I should return Ok!
Err(()) => thread::sleep(Duration::from_secs(delay)),
}
}
println!("error after {} attempts", attempts);
Err(())
}
but if I try to use this function I don't even know how to call it, example:
use hyper::{client::Client, Body, Request};
pub async fn trying_it() {
let hyper_client = Client::new();
let req = Request::builder().uri("http://localhost").body(Body::empty()).unwrap();
// I need to retry this call:
// let resp = hyper_client.request(req).await.unwrap();
let resp = retry(3, 2, hyper_client.request(req).await.unwrap());
println!("{:?}", resp);
}
but the error is:
mismatched types
expected fn pointer `fn() -> std::result::Result<(), ()>`
found struct `hyper::Response<hyper::Body>` rustc E0308
main.rs(26, 16): arguments to this function are incorrect
Should/can I use closures?
What else?
As I noted in the comment, you're trying to retry an async call, this means your retrier needs to be async-aware, it needs to take in an async "thing".
And yes you need closures as well: futures are "one-shot", once done they're done, regardless of success or failure.
And since you're in an async context you should use an async sleep (I used Tokio's) otherwise sleeping will lock up the scheduler.
pub async fn retry<F, Fu>(attempts: u8, delay: u64, f: F) -> Result<(), ()>
where F: Fn() -> Fu,
Fu: Future<Output=Result<(), ()>>{
for n in 0..attempts {
if n >= 1 {
println!("retried {} times", n);
}
if let Ok(()) = f().await {
return Ok(());
}
sleep(Duration::from_secs(delay)).await;
}
println!("error after {} attempts", attempts);
Err(())
}
That means on the caller side you need an async closure, which are not stable, so you need a closure with an async block. And since hyper's requests are (apparently) not clonable, you need to create the request inside the closure / block:
pub async fn trying_it() {
let hyper_client = Client::new();
let resp = retry(3, 2, || async {
let req = Request::builder().uri("http://localhost").body(Body::empty()).unwrap();
hyper_client.request(req).await.map_err(|_| ())?;
Ok(())
}).await;
println!("{:?}", resp);
}
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c9dded3ff61ff7c409d95615e25e262a
With a bit of rejiggering, you can also make the retrier return whatever the retried call returns: the value in case of success, or the last failure.
This actually makes the calling easier since you don't need to muck around with transforming the result, though it makes the retry a bit icky1:
pub async fn retry<F, Fu, V, E>(mut attempts: u8, delay: u64, f: F) -> Result<V, E>
where F: Fn() -> Fu,
Fu: Future<Output=Result<V, E>> {
loop {
match f().await {
Ok(v) => return Ok(v),
Err(e) if attempts == 1 => return Err(e),
_ => {
attempts -= 1;
sleep(Duration::from_secs(delay)).await;
}
};
}
}
pub async fn trying_it() {
let hyper_client = Client::new();
let resp = retry(3, 2, || async {
let req = Request::builder().uri("http://localhost").body(Body::empty()).unwrap();
hyper_client.request(req).await
}).await;
println!("{:?}", resp);
}
1: I would assume there are better solutions using streams and `try_fold` but I've not used streams much.
I'm struggling with error handling cleanly in Rust. Say I have a function that is propogating multiple error types with Box<dyn Error>. To unwrap and handle the error, I'm doing the following:
fn main() {
let json =
get_json_response(format!("{}{}", BASE_URL, LOGIN_URL).as_str()).unwrap_or_else(|e| {
eprintln!("Error: failed to get: {}", e);
std::process::exit(1);
});
}
fn get_json_response(url: &str) -> Result<Value, Box<dyn Error>> {
let resp = ureq::get(url)
.set("Authorization", format!("Bearer {}", API_TOKEN).as_str())
.call()?
.into_json()?;
Ok(resp)
}
This works well. However, if I have multiple calls to get_json_response(), it gets messy to include that same closure over and over.
My solutions is to change it to:
use serde_json::Value;
use std::error::Error;
use ureq;
fn main() {
let json =
get_json_response(format!("{}{}", BASE_URL, LOGIN_URL).as_str()).unwrap_or_else(fail);
}
fn fail(err: Box<dyn Error>) -> ! {
eprintln!("Error: failed to get: {}", err);
std::process::exit(1);
}
This doesn't work because unwrap_or_else() expects a Value to be returned rather than nothing !. I can cheat and change the return value of fail() to -> Value and add Value::Null after the exit(1). It works but feels wrong (and complains).
I could also do unwrap_or_else(|e| fail(e)) which isn't terrible.
Is there an idiomatic way to handle this?
As pointed out by #kmdreko, your code fails to compile because, while ! can be coerced to any T, fn() -> ! cannot be coerced to fn() -> T.
To work around the above, you can declare fail() to return Value, and actually return the "value" of std::process::exit(1). Omitting the semicolon coerces the ! to Value, and you don't have to cheat with a Value::Null:
fn main() {
let _json = get_json_response("...").unwrap_or_else(fail).as_str();
}
fn fail(err: Box<dyn Error>) -> Value {
eprintln!("Error: failed to get: {}", err);
std::process::exit(1)
}
Playground
Why does this code not compile?
use std::{fs, path::Path};
fn main() {
let dir = Path::new("../FileSystem");
if !dir.is_dir() {
println!("Is not a directory");
return;
}
for item in try!(fs::read_dir(dir)) {
let file = match item {
Err(e) => {
println!("Error: {}", e);
return;
}
Ok(f) => f,
};
println!("");
}
println!("Done");
}
This is the error I get
error[E0308]: mismatched types
--> src/main.rs:11:17
|
11 | for item in try!(fs::read_dir(dir)) {
| ^^^^^^^^^^^^^^^^^^^^^^^ expected (), found enum `std::result::Result`
|
= note: expected type `()`
found type `std::result::Result<_, _>`
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
I also tried the question mark operator:
for item in fs::read_dir(dir)? {
Which had a different error:
error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `std::ops::Try`)
--> src/main.rs:11:17
|
11 | for item in fs::read_dir(dir)? {
| ^^^^^^^^^^^^^^^^^^ cannot use the `?` operator in a function that returns `()`
|
= help: the trait `std::ops::Try` is not implemented for `()`
= note: required by `std::ops::Try::from_error`
Previous versions of Rust had a similar error about std::ops::Carrier
Should I avoid try!() and ?? What is the best way to handle errors? Mostly I do it like this:
match error_prone {
Err(e) => {
println!("Error: {}", e);
return;
},
Ok(f) => f,
};
But if I have to use that in a for loop, it's a complete mess
for i in match error_prone {
// match code
} {
// loop code
}
try! is a macro that returns Errs automatically; ? is syntax that does mostly the same thing, but it works with any type that implements the Try trait.
As of Rust 1.22.0, Option implements Try, so it can be used with ?. Before that, ? could only be used in functions that return a Result. try! continues to only work with Results.
As of Rust 1.26.0, main is allowed to return a value that implements Termination. Before that, it doesn't return any value.
As of Rust 1.26.0
Your original code works if you mark main as returning a Result and then return Ok(()) in all the "success" cases:
use std::{fs, io, path::Path};
fn main() -> Result<(), io::Error> {
let dir = Path::new("../FileSystem");
if !dir.is_dir() {
println!("Is not a directory");
return Ok(());
}
for item in fs::read_dir(dir)? {
let file = match item {
Err(e) => {
println!("Error: {}", e);
return Ok(());
}
Ok(f) => f,
};
println!("");
}
println!("Done");
Ok(())
}
Before that
This is how you might transform your code to use ?:
use std::{error::Error, fs, path::Path};
fn print_dir_contents() -> Result<String, Box<Error>> {
let dir = Path::new("../FileSystem");
if !dir.is_dir() {
return Err(Box::from("Is not a directory!"));
}
for entry in fs::read_dir(dir)? {
let path = entry?.path();
let file_name = path.file_name().unwrap();
println!("{}", file_name.to_string_lossy());
}
Ok("Done".into())
}
fn main() {
match print_dir_contents() {
Ok(s) => println!("{}", s),
Err(e) => println!("Error: {}", e.to_string()),
}
}
There's a lot of error handling here that you might not expect - other languages don't tend to require it! But they exist in other languages - Rust just makes you know it. Here are the errors:
entry?
IO errors can happen during iteration.
path.file_name().unwrap()
Not all paths have file names. We can unwrap this because read_dir won't give us a path without a file name.
file_name.to_string_lossy()
You can also to_str and throw an error, but it's nicer to do this. This error exists because not all file names are valid Unicode.
try! and ? throw errors into the return value, converting them to Box::Error. It's actually more reasonable to return an amalgamated error of all the things that can go wrong. Luckily io::Error is just the right type:
use std::io;
// ...
fn print_dir_contents() -> Result<String, io::Error> {
// ...
if !dir.is_dir() {
return Err(io::Error::new(io::ErrorKind::Other, "Is not a directory!"));
}
// ...
}
Frankly, though, this check is already in fs::read_dir, so you can actually just remove the if !dis.is_dir altogether:
use std::{fs, io, path::Path};
fn print_dir_contents() -> Result<String, io::Error> {
let dir = Path::new("../FileSystem");
for entry in fs::read_dir(dir)? {
let path = entry?.path();
let file_name = path.file_name().unwrap();
println!("{}", file_name.to_string_lossy());
}
Ok("Done".into())
}
fn main() {
match print_dir_contents() {
Ok(s) => println!("{}", s),
Err(e) => println!("Error: {}", e.to_string()),
}
}
The ques_in_main RFC got merged recently. Once it's completed, the syntax in the question will indeed compile just fine and work as intended, provided the try!() calls are replaced with the ? operator.
As of Rust 1.26, Rust supports a return value from main(), and thus supports the use of the error-check operator ? (or equivalently the try!() macro) in main() when main() is defined to return a Result:
extern crate failure;
use failure::Error;
use std::fs::File;
type Result<T> = std::result::Result<T, Error>;
fn main() -> Result<()> {
let mut _file = File::open("foo.txt")?; // does not exist; returns error
println!("the file is open!");
Ok(())
}
The above compiles and returns a file not found error (assuming foo.txt does not exist in the local path).
Rust playground example
Veedrac's answer helped me too, although the OP's question is slightly different. While reading the Rust documentation, I saw this snippet:
use std::fs::File;
use std::io::prelude::*;
let mut file = File::open("foo.txt")?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
assert_eq!(contents, "Hello, world!");
Though in the Rust Book they point out the centrality of the main function, if you run this inside it you'll get a similar error. If you wrap the code inside a function handling the errors the aforementioned snippet works:
use std::error::Error;
use std::io::prelude::*;
use std::fs::File;
fn print_file_content() -> Result<String, Box<Error>> {
let mut f = File::open("foo.txt")?;
let mut contents = String::new();
f.read_to_string(&mut contents)?;
println!("The content: {:?}", contents);
Ok("Done".into())
}
fn main() {
match print_file_content() {
Ok(s) => println!("{}", s),
Err(e) => println!("Error: {}", e.to_string()),
}
}
P.S. I'm learning Rust so these snippets are not intended as good Rust coding :)
I have a function that calls another function which returns a Result. I need to check if the Result is Ok or Err and if it is an Err, I need to return early from my function. This is what I'm doing now:
match callable(&mut param) {
Ok(_v) => (),
Err(_e) => return,
};
Is there a more idiomatic Rust way to do this?
You can create a macro:
macro_rules! unwrap_or_return {
( $e:expr ) => {
match $e {
Ok(x) => x,
Err(_) => return,
}
}
}
fn callable(param: &mut i32) -> Result<i32, ()> {
Ok(*param)
}
fn main() {
let mut param = 0;
let res = unwrap_or_return!(callable(&mut param));
println!("{:?}", res);
}
Note that I wouldn't recommend discarding the errors. Rust's error handling is pretty ergonomic, so I would return the error, even if it is only to log it:
fn callable(param: &mut i32) -> Result<i32, ()> {
Ok(*param)
}
fn run() -> Result<(), ()> {
let mut param = 0;
let res = callable(&mut param)?;
println!("{:?}", res);
Ok(())
}
fn main() {
if let Err(()) = run() {
println!("Oops, something went wrong!");
}
}
If both functions return Result<doesn't matter, same T> you can just put a ? at the end of line of call.
fn caller() -> Result<Str, i32> {
let number = job()?; // <-- if job return error this function return/end here
// otherwise the value of Ok will assign to number
Ok(format!("the number is {}", number))
}
fn job() -> Result<i32, i32> {
// do something
Err(3)
}
You can use same pattern for Option<T> too.
You can use my unwrap_or crate to accomplish this.
You can do:
unwrap_or_ok!(callable(&mut param), _, return);
And if you want the result and to return the error, you can do:
let v = unwrap_or_ok!(callable(&mut param), error, return error);
Rust 1.65.0 has stabilized let-else statements, which enables you to write:
let Ok(_v) = callable(&mut param) else { return };
Why does this code not compile?
use std::{fs, path::Path};
fn main() {
let dir = Path::new("../FileSystem");
if !dir.is_dir() {
println!("Is not a directory");
return;
}
for item in try!(fs::read_dir(dir)) {
let file = match item {
Err(e) => {
println!("Error: {}", e);
return;
}
Ok(f) => f,
};
println!("");
}
println!("Done");
}
This is the error I get
error[E0308]: mismatched types
--> src/main.rs:11:17
|
11 | for item in try!(fs::read_dir(dir)) {
| ^^^^^^^^^^^^^^^^^^^^^^^ expected (), found enum `std::result::Result`
|
= note: expected type `()`
found type `std::result::Result<_, _>`
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
I also tried the question mark operator:
for item in fs::read_dir(dir)? {
Which had a different error:
error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `std::ops::Try`)
--> src/main.rs:11:17
|
11 | for item in fs::read_dir(dir)? {
| ^^^^^^^^^^^^^^^^^^ cannot use the `?` operator in a function that returns `()`
|
= help: the trait `std::ops::Try` is not implemented for `()`
= note: required by `std::ops::Try::from_error`
Previous versions of Rust had a similar error about std::ops::Carrier
Should I avoid try!() and ?? What is the best way to handle errors? Mostly I do it like this:
match error_prone {
Err(e) => {
println!("Error: {}", e);
return;
},
Ok(f) => f,
};
But if I have to use that in a for loop, it's a complete mess
for i in match error_prone {
// match code
} {
// loop code
}
try! is a macro that returns Errs automatically; ? is syntax that does mostly the same thing, but it works with any type that implements the Try trait.
As of Rust 1.22.0, Option implements Try, so it can be used with ?. Before that, ? could only be used in functions that return a Result. try! continues to only work with Results.
As of Rust 1.26.0, main is allowed to return a value that implements Termination. Before that, it doesn't return any value.
As of Rust 1.26.0
Your original code works if you mark main as returning a Result and then return Ok(()) in all the "success" cases:
use std::{fs, io, path::Path};
fn main() -> Result<(), io::Error> {
let dir = Path::new("../FileSystem");
if !dir.is_dir() {
println!("Is not a directory");
return Ok(());
}
for item in fs::read_dir(dir)? {
let file = match item {
Err(e) => {
println!("Error: {}", e);
return Ok(());
}
Ok(f) => f,
};
println!("");
}
println!("Done");
Ok(())
}
Before that
This is how you might transform your code to use ?:
use std::{error::Error, fs, path::Path};
fn print_dir_contents() -> Result<String, Box<Error>> {
let dir = Path::new("../FileSystem");
if !dir.is_dir() {
return Err(Box::from("Is not a directory!"));
}
for entry in fs::read_dir(dir)? {
let path = entry?.path();
let file_name = path.file_name().unwrap();
println!("{}", file_name.to_string_lossy());
}
Ok("Done".into())
}
fn main() {
match print_dir_contents() {
Ok(s) => println!("{}", s),
Err(e) => println!("Error: {}", e.to_string()),
}
}
There's a lot of error handling here that you might not expect - other languages don't tend to require it! But they exist in other languages - Rust just makes you know it. Here are the errors:
entry?
IO errors can happen during iteration.
path.file_name().unwrap()
Not all paths have file names. We can unwrap this because read_dir won't give us a path without a file name.
file_name.to_string_lossy()
You can also to_str and throw an error, but it's nicer to do this. This error exists because not all file names are valid Unicode.
try! and ? throw errors into the return value, converting them to Box::Error. It's actually more reasonable to return an amalgamated error of all the things that can go wrong. Luckily io::Error is just the right type:
use std::io;
// ...
fn print_dir_contents() -> Result<String, io::Error> {
// ...
if !dir.is_dir() {
return Err(io::Error::new(io::ErrorKind::Other, "Is not a directory!"));
}
// ...
}
Frankly, though, this check is already in fs::read_dir, so you can actually just remove the if !dis.is_dir altogether:
use std::{fs, io, path::Path};
fn print_dir_contents() -> Result<String, io::Error> {
let dir = Path::new("../FileSystem");
for entry in fs::read_dir(dir)? {
let path = entry?.path();
let file_name = path.file_name().unwrap();
println!("{}", file_name.to_string_lossy());
}
Ok("Done".into())
}
fn main() {
match print_dir_contents() {
Ok(s) => println!("{}", s),
Err(e) => println!("Error: {}", e.to_string()),
}
}
The ques_in_main RFC got merged recently. Once it's completed, the syntax in the question will indeed compile just fine and work as intended, provided the try!() calls are replaced with the ? operator.
As of Rust 1.26, Rust supports a return value from main(), and thus supports the use of the error-check operator ? (or equivalently the try!() macro) in main() when main() is defined to return a Result:
extern crate failure;
use failure::Error;
use std::fs::File;
type Result<T> = std::result::Result<T, Error>;
fn main() -> Result<()> {
let mut _file = File::open("foo.txt")?; // does not exist; returns error
println!("the file is open!");
Ok(())
}
The above compiles and returns a file not found error (assuming foo.txt does not exist in the local path).
Rust playground example
Veedrac's answer helped me too, although the OP's question is slightly different. While reading the Rust documentation, I saw this snippet:
use std::fs::File;
use std::io::prelude::*;
let mut file = File::open("foo.txt")?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
assert_eq!(contents, "Hello, world!");
Though in the Rust Book they point out the centrality of the main function, if you run this inside it you'll get a similar error. If you wrap the code inside a function handling the errors the aforementioned snippet works:
use std::error::Error;
use std::io::prelude::*;
use std::fs::File;
fn print_file_content() -> Result<String, Box<Error>> {
let mut f = File::open("foo.txt")?;
let mut contents = String::new();
f.read_to_string(&mut contents)?;
println!("The content: {:?}", contents);
Ok("Done".into())
}
fn main() {
match print_file_content() {
Ok(s) => println!("{}", s),
Err(e) => println!("Error: {}", e.to_string()),
}
}
P.S. I'm learning Rust so these snippets are not intended as good Rust coding :)