Handling a Response from a Result using match - rust

I'm trying to return a Result from a function and extract it's return.
I'm using i32 and a &str and I get a mismatch type error in the match statment (Can't use two different types in a match).
How do I fix this?
fn use_result(par: i32)-> Result<i32, &'static str> {
if par == 0 {
Err("some error")
} else {
println!("par is 1");
Ok(par)
}
}
fn main() {
// Result
let res = match use_result(1) {
Ok(v) => v,
Err(e) => e,
};
}
//Do something with res: v or res: e
}

In Rust, every variable has a single type. In the code you have now, res is either a &'static str or an i32, which is not allowed.
Your options are:
Return early
fn main() {
let res: i32 = match use_result(1) {
Ok(v) => v,
Err(e) => return,
};
}
Different code in each match arm
fn main() {
match use_result(1) {
Ok(v) => {
handle_success(v);
},
Err(e) => {
handle_error(e);
},
};
}
Return an enum
Enums allow you to express that a type is "one of these possible variants" in a type safe way:
enum IntOrString {
Int(i32),
String(&'static str),
}
fn main() {
let i_or_s: IntOrString = match use_result(1) {
Ok(v) => IntOrString::Int(v),
Err(e) => IntOrString::String(e),
};
}
But this is a bit weird, since Result<i32, &'static str> is already an enum, if you want to do anything with an IntOrString you'll need to match on it later on (or an if let, etc).
Panic
fn main() {
let res: i32 = match use_result(1) {
Ok(v) => v,
Err(e) => panic!("cannot be zero"),
};
}
This is more cleanly expressed as use_result(1).unwrap(). It's usually not what you want, since it doesn't allow the caller of the function to recover/handle the error. But if you're calling this from main(), the error has nowhere else to propagate to, so unwrapping is usually OK.

Related

Concise logging when matching Result

I am working on a large project in Rust with many moving pieces. One of my agendas is to log an error if something goes wrong in any part so I can debug later.
However, it seems for every function call the conciseness has vanished, plausibly due to my naivete.
How can I make this more concise? I looked at this question which essentially asks to use a match to capture both OK() and Err.
fn pathway_1(x: &str, y: &str) -> Option<Vec<String>> {
let unique_numbers = match query_unique_numbers(&x, &y) {
Ok(r) => r
Err(e) => {
log::error!(target: "normal", "Could not query unique numbers. Err {e}");
return None;
}
}
let unique_people = match query_unique_people(&unique_numbers, &y) {
Ok(r) => r
Err(e) => {
log::error!(target: "normal", "Could not query unique people. Err {e}");
return None;
}
}
let relevant_things_by_people = match query_relevant_things(&unique_people, &y) {
Ok(r) => r
Err(e) => {
log::error!(target: "normal", "Could not query relevant things. Err {e}");
return None;
}
}
/// Many such function calls below.
Some(vector_of_strings)
}
You can use .ok() to convert a Result into an Option and then use ? to propagate it.
Logging on each result could be achieved with .map_err() to apply a "transformation" to the error. It may be slightly better to use .inspect_err() (when stabilized) or .tap_err() (from the tap crate), though since the error is not used after logging, it doesn't really matter:
fn pathway_1(x: &str, y: &str) -> Option<Vec<String>> {
let unique_numbers = query_unique_numbers(&x, &y)
.map_err(|e| { log::error!(target: "normal", "Could not query unique numbers. Err {e}"); })
.ok()?;
let unique_people = query_unique_people(&unique_numbers, &y)
.map_err(|e| { log::error!(target: "normal", "Could not query unique people. Err {e}"); })
.ok()?;
let relevant_things_by_people = query_relevant_things(&unique_people, &y)
.map_err(|e| { log::error!(target: "normal", "Could not query relevant things. Err {e}"); })
.ok()?;
/// Many such function calls below.
Some(vector_of_strings)
}
However, I would not do it like this. You seem to be using Option in the return type to indicate failure. If that is the case, you should return a Result. And if you do that, you can create a proper encompassing error type that can express what step failed, with what original error, and the display logic (using something like the thiserror crate):
use thiserror::Error;
#[derive(Error, Debug)]
enum QueryError {
#[error("Could not query unique numbers. Err {0}")]
UniqueNumbers(SomeError),
#[error("Could not query unique people. Err {0}")]
UniquePeople(SomeError),
#[error("Could not query relevant things. Err {0}")]
RelevantThings(SomeError),
}
fn pathway_1(x: &str, y: &str) -> Result<Vec<String>, QueryError> {
let unique_numbers = query_unique_numbers(&x, &y)
.map_err(QueryError::UniqueNumbers)?;
let unique_people = query_unique_people(&unique_numbers, &y)
.map_err(QueryError::UniquePeople)?;
let relevant_things_by_people = query_relevant_things(&unique_people, &y)
.map_err(QueryError::RelevantThings)?;
/// Many such function calls below.
Ok(vector_of_strings)
}
Then you can handle all errors in one place in the caller. For example:
match pathway_1(x, y) {
Ok(vector_of_strings) => {
// do something
}
Err(e) => {
log::error!(target: "normal", "{e}");
}
}
You can create a custom method on Result, log(), that logs the error with context:
trait ResultExt<T> {
fn log(self, context: &str) -> Option<T>;
}
impl<T, E: std::fmt::Display> ResultExt<T> for Result<T, E> {
fn log(self, context: &str) -> Option<T> {
match self {
Ok(v) => Some(v),
Err(err) => {
log::error!(target: "normal", "{context}. Err {err}");
None
}
}
}
}
fn pathway_1(x: &str, y: &str) -> Option<Vec<String>> {
let unique_numbers = query_unique_numbers(&x, &y).log("Could not query unique numbers")?;
let unique_people =
query_unique_people(&unique_numbers, &y).log("Could not query unique people")?;
let relevant_things_by_people =
query_relevant_things(&unique_people, &y).log("Could not query relevant things")?;
/// Many such function calls below.
Some(vector_of_strings)
}

Assigning returned value in a match arm to a new variable

I am working on my first Rust project, a CLI application to find large files on a local filesystem. Rust has excellent documentation regarding match statements but I do not see how I can assign a value returned by the function passed to the match statement in a new variable:
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
let mut results_map: Option<Results>;
match search(&config.root_path) {
Err(e) => eprintln!("Error when calling run(): {:?}", e),
Ok(results) => results_map = results), // how to properly assign value returned by function here?
}
Ok(())
}
pub struct Results {
result_map: HashMap<String, u64>,
}
pub fn search(path: &str) -> Result<Results, io::Error> {
let root_path = Path::new(path);
let mut results = Results { result_map: HashMap::<String, u64>::new()};
match visit_dirs(root_path, &mut results) {
Err(e) => eprintln!("Error calling visit_dirs() from search(): {:?}", e),
_ => (),
}
Ok(results)
}
The functions you're calling in the match statements, search and visit_dirs return Result types. You have to use one of the arms of the match statement to cover the Ok (happy) case, next to where you're already covering the Err (error) case:
match search(&config.root_path) {
Ok(results) => return results,
Err(e) => eprintln!("Error when calling run(): {:?}", e),
}
Or you can assign the result of the match expression to a new variable:
let new_variable = match search(&config.root_path) {
Ok(results) => results, // no return here
Err(e) => {
eprintln!("Error when calling run(): {:?}", e)
// Either choose to panic (not recommended)
// panic!("Bad times lie ahead")
// Or propagate the error from your own Result
return Err(e);
},
}
You may want to look into the ? operator to simplify this in the common case.

Match Error Result and exit program on variable assignment

I do check function results on assignment using match statement. In some cases I want to exit the program with an error message like panic!() does. But how can I create a function or macro that can be used everywhere?
Example:
let args = match args::Args::parse() {
Ok(args) => args,
Err(e) => someerror("bla")
};
let mut statedoc = match state_loader.load() {
Ok(states) => states,
Err(e) => someerror("blub")
};
What does someerror() need to return to work everywhere?
One way is to use diverging function. Use the following syntax:
fn someerror(msg: &str) -> ! { // Note the `-> !` here
eprintln!("Error: {}", msg);
panic!();
}
fn main() {
let r: Result<i32, &str> = Err("hello");
let x = match r {
Ok(x) => x,
Err(e) => someerror(e),
};
println!("x = {}", x);
}
Remember that main can return a Result and you can use the ?-operator everywhere:
fn foo() -> Result<i32, &'static str> {
Err("Nope!")
}
fn main() -> Result<(), &'static str> {
let x = 5 * foo()?;
println!("{}", x);
Ok(())
}
When executed, the above program will just print "Error: Nope!" and have an exit status not equal to zero. To support more kinds of errors, you can have a a custom enum to wrap those and appropriate implementations of Into, so you can just do let args = args::Args::parse()?;. Any errors will bubble up to main() and cause the error to be printed out.

Rust String vs &str iterators

I'm trying to write a function which accepts a list of tokens. But I'm having problems making it general enough to handle two pretty similar calls:
let s = String::from("-abc -d --echo");
parse( s.split_ascii_whitespace() );
parse( std::env::args() );
String::split_ascii_whitespace() returns std::str:SplitAsciiWhitespace which implements Iterator<Item=&'a str>.
std::env::args() returns std::env::Args which implements Iterator<Item=String>.
Is there a way for me to write a function signature for parse that will accept both methods?
My solution right now requires duplicating function bodies:
fn main() {
let s = String::from("-abc -d --echo");
parse_args( s.split_ascii_whitespace() );
parse_env( std::env::args() );
}
fn parse_env<I: Iterator<Item=String>>(mut it: I) {
loop {
match it.next() {
None => return,
Some(s) => println!("{}",s),
}
}
}
fn parse_args<'a, I: Iterator<Item=&'a str>>(mut it: I) {
loop {
match it.next() {
None => return,
Some(s) => println!("{}",s),
}
}
}
If not possible, then some advice on how to use the traits so the functions can use the same name would be nice.
You can require the item type to be AsRef<str>, which will include both &str and String:
fn parse<I>(mut it: I)
where
I: Iterator,
I::Item: AsRef<str>,
{
loop {
match it.next() {
None => return,
Some(s) => println!("{}", s.as_ref()),
}
}
}
Depending on your use case, you could try:
fn main() {
let s = String::from("-abc -d --echo");
parse( s.split_ascii_whitespace() );
parse( std::env::args() );
}
fn parse<T: std::borrow::Borrow<str>, I: Iterator<Item=T>>(mut it: I) {
loop {
match it.next() {
None => return,
Some(s) => println!("{}",s.borrow()),
}
}
}
I used Borrow as a means to get to a &str, but your concrete use case may be served by other, possibly custom, traits.

How do you unwrap a Result on Ok or return from the function on Err?

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 };

Resources