Concise logging when matching Result - rust

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

Related

Rust : Cleanest way to transfert result and catch an error in when calling function?

in order to learn Rust, I try to create small snippets to apply what we learn in the Rust book and implement good practices.
Have a small function to list content of a repository :
use std::{io, fs, path::PathBuf, path::Path};
pub fn get_directory_content(path: &str) -> Result<Vec<PathBuf>, io::Error> {
let _path: bool = Path::new(path).is_dir();
match _path {
true => {
let mut result = vec![];
for file in fs::read_dir(path).unwrap() {
result.push(file.unwrap().path());
}
Ok(result)
},
false => Err(io::Error::new(io::ErrorKind::Other, " is not a directory")),
}
}
my goal is to be able to catch the error if the folder does not exist without triggering a panic.
in main.rs :
mod utils;
fn main() {
let directory = "./qsdsqd";
let test = utils::get_directory_content(directory).unwrap();
println!("{:?}", a);
}
if directory exist : ok, unwrap is happy. But does anyone know a "trick" for get the content of the error in var test ? Also, can we put the name of a variable in io::ErrorKind::Other to get more precision (here : &path) ?
Next try
fn main() {
let directory = "./qsdqsd";
let a = match utils::get_directory_content(directory){
Err(e) => println!("an error: {:?}", e),
Ok(c) => println!("{:?}", c),
};
println!("{:?}", a);
}
When error, ok, we have message, but here, if we put a correct folder : a "just" print result but content is empty, and we can't say Ok(c) => c for just return Ok content from function :/
Have a small function to list content of a repository :
That's already a pretty bad start, because it combines a TOCTOU with unnecessary extra work: if you're checking is_dir then trying to read the directory, it's possible for the entry to get deleted or swapped from under you.
This is a shame, since read_dir already does exactly what you want:
pub fn get_directory_content(path: &str) -> Result<Vec<PathBuf>, io::Error> {
let mut result = vec![];
for file in fs::read_dir(path)? {
result.push(file.unwrap().path());
}
Ok(result)
}
And you can apply this to the individual entries as well:
pub fn get_directory_content(path: &str) -> Result<Vec<PathBuf>, io::Error> {
let mut result = vec![];
for file in fs::read_dir(path)? {
result.push(file?.path());
}
Ok(result)
}
When error, ok, we have message, but here, if we put a correct folder : a "just" print result but content is empty, and we can't say Ok(c) => c for just return Ok content from function :/
Sure you can, however you still have to do something for the Err case: as most things in Rust, match is an expression, so all the branches need to return values of the same type... or not return at all:
let a = match get_directory_content(directory) {
Err(e) => {
println!("an error: {:?}", e);
return;
}
Ok(c) => c,
};
return has type !, which is Rust's "bottom" type: it's compatible with everything, because return does not "terminate", and thus there's npo reason for it to be incompatible with anything.
Alternatively, you could update main to return a Result as well, though that also requires updating it to return a value:
fn main() -> Result<(), io::Error> {
let directory = "./.config";
let a = get_directory_content(directory)?;
println!("{:?}", a);
Ok(())
}
You need to return c from your match statement.
Further, you need to do something in the Err case other than just print. What should a be in the error case?
I assume that you simply want to end the program, so I inserted a return.
mod utils {
use std::{fs, io, path::Path, path::PathBuf};
pub fn get_directory_content(path: &str) -> Result<Vec<PathBuf>, io::Error> {
let _path: bool = Path::new(path).is_dir();
match _path {
true => {
let mut result = vec![];
for file in fs::read_dir(path).unwrap() {
result.push(file.unwrap().path());
}
Ok(result)
}
false => Err(io::Error::new(io::ErrorKind::Other, " is not a directory")),
}
}
}
fn main() {
let directory = "./qsdqsd";
let a = match utils::get_directory_content(directory) {
Err(e) => {
println!("an error: {:?}", e);
return;
}
Ok(c) => {
println!("{:?}", c);
c
}
};
println!("{:?}", a);
}
["./qsdqsd/a.txt"]
["./qsdqsd/a.txt"]
DISCLAIMER: My answer is very much superficial. #Masklinn goes into much more detail about the "cleanest way" and other issues with the given code.
Because this is the accepted answer (at the time of writing), here is how a "cleanest way" version of the code could look like:
use std::{fs, io, path::PathBuf};
pub fn get_directory_content(path: &str) -> Result<Vec<PathBuf>, io::Error> {
let mut result = vec![];
for file in fs::read_dir(path)? {
result.push(file?.path());
}
Ok(result)
}
fn main() {
let directory = "./qsdqsd2";
let a = match get_directory_content(directory) {
Err(e) => {
println!("an error: {:?}", e);
return;
}
Ok(c) => c,
};
println!("{:?}", a);
}
["./qsdqsd/a.txt"]
Alternatively, you could have main() return a Result, which makes this even cleaner:
use std::{fs, io, path::PathBuf};
pub fn get_directory_content(path: &str) -> Result<Vec<PathBuf>, io::Error> {
let mut result = vec![];
for file in fs::read_dir(path)? {
result.push(file?.path());
}
Ok(result)
}
fn main() -> Result<(), io::Error> {
let directory = "./qsdqsd";
let a = get_directory_content(directory)?;
println!("{:?}", a);
Ok(())
}

How to convert Vec<Result<Option<(u32, u32)>, Error>> to Result<HashMap<u32, 32>, Error> using iterator only(without for)?

Apologize for confusing title.
Say I have a user_ids: Vec<u32> and there is a function fn get_year(user_id: u32) -> Result<Option<u32>, Error>(this function involve db lookup`
And I would like to get years of all those user_ids and concert it into a hashmap where key is the id and value is the year. If it should return error immediately if there is one get_year return error.
I am able to get it working with for
use std::collections::HashMap;
#[derive(Debug)]
enum Error {
ConnectionFail
}
fn get_year(user_id: u32) -> Result<Option<u32>, Error> {
Err(Error::ConnectionFail)
}
fn get_age_lookup(user_ids: Vec<u32>) -> Result<HashMap<u32, u32>, Error> {
let mut age_lookup = HashMap::new();
for &user_id in user_ids.iter() {
let year_result = get_year(user_id)?;
match year_result {
None => (),
Some(year) => {age_lookup.insert(user_id, year);}
};
};
Ok(age_lookup)
}
fn main() {
let age_lookup = get_age_lookup(vec![1, 2, 3]);
println!("result: {:?}!", age_lookup);
}
But not able to do it without for. I am able to get it working but that require 2 iterators which is probably not efficient. I wonder if there is a way to make it work with only 1 iteration without for`?
fn get_age_lookup(user_ids: Vec<u32>) -> Result<HashMap<u32, u32>, Error> {
let age_tuples =
user_ids
.into_iter()
.map(|user_id| {
get_year(user_id)
.map(|year_result| match year_result {
None => None,
Some(year) => Some((user_id, year))
})
})
.collect::<Result<Vec<Option<(u32, u32)>>, Error>>()?;
Ok(age_tuples.into_iter().filter_map(|x| x).collect::<HashMap<u32, u32>>())
}
}
You are very close. The missing ingredient here is Result::transpose(), which turns a Result<Option<T>, E> into a Option<Result<T, E>>. You can filter_map() over that, which will eliminate the Ok(None) values and simultaneously unwrap the nested Option, so you'll have an iterator of Result<T, E> at that point. Finally, collect() can convert into a collection wrapped by a Result -- if any item in the sequence is Err, it will short-circuit and propagate the error.
fn get_age_lookup(user_ids: Vec<u32>) -> Result<HashMap<u32, u32>, Error> {
user_ids
.into_iter()
.map(|user_id| {
get_year(user_id)
.map(|year_result| match year_result {
None => None,
Some(year) => Some((user_id, year))
})
})
.filter_map(|x| x.transpose())
.collect()
}
(Playground)
As an aside, you can additionally simplify your match using the Option::map() method, which applies a transformation to Ok values. So this:
match year_result {
None => None,
Some(year) => Some((user_id, year))
}
Can become:
year_result.map(|year| (user_id, year))

Handling a Response from a Result using match

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.

Which signature is most effective when using multiple conditions or Results? How to bubble errors correctly?

Introduction
I'm learning rust and have been trying to find the right signature for using multiple Results in a single function and then returning either correct value, or exit the program with a message.
So far I have 2 different methods and I'm trying to combine them.
Context
This is what I'm trying to achieve:
fn blur(image: DynamicImage, amount: &str) -> DynamicImage {
let amount = parse_between_or_error_out("blur", amount, 0.0, 10.0);
image.brighten(amount)
}
This is what I have working now, but would like to refactor.
fn blur(image: DynamicImage, amount: &str) -> DynamicImage {
match parse::<f32>(amount) {
Ok(amount) => {
verify_that_value_is_between("blur", amount, 0.0, 10.0);
image.blur(amount)
}
_ => {
println!("Error");
process::exit(1)
}
}
}
Combining these methods
Now here's the two working methods that I'm trying to combine, to achieve this.
fn parse<T: FromStr>(value: &str) -> Result<T, <T as FromStr>::Err> {
value.parse::<T>()
}
fn verify_that_value_is_between<T: PartialOrd + std::fmt::Display>(
name: &str,
amount: T,
minimum: T,
maximum: T,
) {
if amount > maximum || amount < minimum {
println!(
"Error: Expected {} amount to be between {} and {}",
name, minimum, maximum
);
process::exit(1)
};
println!("- Using {} of {:.1}/{}", name, amount, maximum);
}
Here's what I tried
I have tried the following. I realise I'm likely doing a range of things wrong. This is because I'm still learning Rust, and I'd like any feedback that helps me learn how to improve.
fn parse_between_or_error_out<T: PartialOrd + FromStr + std::fmt::Display>(
name: &str,
amount: &str,
minimum: T,
maximum: T,
) -> Result<T, <T as FromStr>::Err> {
fn error_and_exit() {
println!(
"Error: Expected {} amount to be between {} and {}",
name, minimum, maximum
);
process::exit(1);
}
match amount.parse::<T>() {
Ok(amount) => {
if amount > maximum || amount < minimum {
error_and_exit();
};
println!("- Using {} of {:.1}/{}", name, amount, maximum);
amount
}
_ => {
error_and_exit();
}
}
}
Currently this looks quite messy, probably I'm using too many or the wrong types and the error needs to be in two places (hence the inlined function, which I know is not good practice).
Full reproducible example.
The question
How to best combine logic that is using a Result and another condition (or Result), exit with a message or give T as a result?
Comments on any of the mistakes are making are very welcome too.
You can use a crate such as anyhow to bubble your events up and handle them as needed.
Alternatively, you can write your own trait and implement it on Result.
trait PrintAndExit<T> {
fn or_print_and_exit(&self) -> T;
}
Then use it by calling the method on any type that implements it:
fn try_get_value() -> Result<bool, MyError> {
MyError { msg: "Something went wrong".to_string() }
}
let some_result: Result<bool, MyError> = try_get_value();
let value: bool = some_result.or_print_and_exit();
// Exits with message: "Error: Something went wrong"
Implementing this trait on Result could be done with:
struct MyError {
msg: String,
}
impl<T> PrintAndExit<T> for Result<T, MyError> {
fn or_print_and_exit(&self) -> T {
match self {
Ok(val) => val,
Err(e) => {
println!("Error: {}", e.msg);
std::process::exit(1);
},
}
}
}
Here are a few DRY tricks.
tl;dr:
Convert other Errors into your unified error type(s) with impl From<ExxError> for MyError;
In any function that may result in an Error, use ? as much as you can. Return Result<???, MyError> (*). ? will utilize the implicit conversion.
(*) Only if MyError is an appropriate type for the function. Always create or use the most appropriate error types. (Kinda obvious, but people often treat error types as a second-class code, pun intended)
Recommendations are in the comments.
use std::error::Error;
use std::str::FromStr;
// Debug and Display are required by "impl Error" below.
#[derive(Debug)]
enum ProcessingError {
NumberFormat{ message: String },
NumberRange{ message: String },
ProcessingError{ message: String },
}
// Display will be used when the error is printed.
// No need to litter the business logic with error
// formatting code.
impl Display for ProcessingError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ProcessingError::NumberFormat { message } =>
write!(f, "Number format error: {}", message),
ProcessingError::NumberRange { message } =>
write!(f, "Number range error: {}", message),
ProcessingError::ProcessingError { message } =>
write!(f, "Image processing error: {}", message),
}
}
}
impl Error for ProcessingError {}
// FromStr::Err will be implicitly converted into ProcessingError,
// when ProcessingError is needed. I guess this is what
// anyhow::Error does under the hood.
// Implement From<X> for ProcessingError for every X error type
// that your functions like process_image() may encounter.
impl From<FromStr::Err> for ProcessingError {
fn from(e: FromStr::Err) -> ProcessingError {
ProcessingError::NumberFormat { message: format!("{}", e) }
}
}
pub fn try_parse<T: FromStr>(value: &str) -> Result<T, ProcessingError> {
// Note ?. It will implicitly return
// Err(ProcessingError created from FromStr::Err)
Ok (
value.parse::<T>()?
)
}
// Now, we can have each function only report/handle errors that
// are relevant to it. ? magically eliminates meaningless code like
// match x { ..., Err(e) => Err(e) }.
pub fn parse_between<T>(value: &str, min_amount: T, max_amount: T)
-> Result<T, ProcessingError>
where
T: FromStr + PartialOrd + std::fmt::Display,
{
let amount = try_parse::<T>(value)?;
if amount > max_amount || amount < min_amount {
Err(ProcessingError::NumberRange {
message: format!(
"Expected value to be between {} and {} but received {}",
min_amount,
max_amount,
amount)
})
} else {
Ok(amount)
}
}
main.rs
use image::{DynamicImage};
use std::fmt::{Debug, Formatter, Display};
fn blur(image: DynamicImage, value: &str)
-> Result<DynamicImage, ProcessingError>
{
let min_amount = 0.0;
let max_amount = 10.0;
// Again, note ? in the end.
let amount = parse_between(value, min_amount, max_amount)?;
image.blur(amount)
}
// All processing extracted into a function, whose Error
// then can be handled by main().
fn process_image(image: DynamicImage, value: &str)
-> Result<DynamicImage, ProcessingError>
{
println!("applying blur {:.1}/{:.1}...", amount, max_amount);
image = blur(image, value);
// save image ...
image
}
fn main() {
let mut image = DynamicImage::new(...);
image = match process_image(image, "1") {
Ok(image) => image,
// No need to reuse print-and-exit functionality. I doubt
// you want to reuse it a lot.
// If you do, and then change your mind, you will have to
// root it out of all corners of your code. Better return a
// Result and let the caller decide what to do with errors.
// Here's a single point to process errors and exit() or do
// something else.
Err(e) => {
println!("Error processing image: {:?}", e);
std::process::exit(1);
}
}
}
Sharing my results
I'll share my results/answer as well for other people who are new to Rust. This answer is based on that of #Acidic9's answer.
The types seem to be fine
anyhow looks to be the de facto standard in Rust.
I should have used a trait and implement that trait for the Error type.
I believe the below example is close to what it might look like in the wild.
// main.rs
use image::{DynamicImage};
use app::{parse_between, PrintAndExit};
fn main() {
// mut image = ...
image = blur(image, "1")
// save image
}
fn blur(image: DynamicImage, value: &str) -> DynamicImage {
let min_amount = 0.0;
let max_amount = 10.0;
match parse_between(value, min_amount, max_amount).context("Input error") {
Ok(amount) => {
println!("applying blur {:.1}/{:.1}...", amount, max_amount);
image.blur(amount)
}
Err(error) => error.print_and_exit(),
}
}
And the implementation inside the apps library, using anyhow.
// lib.rs
use anyhow::{anyhow, Error, Result};
use std::str::FromStr;
pub trait Exit {
fn print_and_exit(self) -> !;
}
impl Exit for Error {
fn print_and_exit(self) -> ! {
eprintln!("{:#}", self);
std::process::exit(1);
}
}
pub fn try_parse<T: FromStr>(value: &str) -> Result<T, Error> {
match value.parse::<T>() {
Ok(value) => Ok(value),
Err(_) => Err(anyhow!("\"{}\" is not a valid value.", value)),
}
}
pub fn parse_between<T>(value: &str, min_amount: T, max_amount: T) -> Result<T, Error>
where
T: FromStr + PartialOrd + std::fmt::Display,
{
match try_parse::<T>(value) {
Ok(amount) => {
if amount > max_amount || amount < min_amount {
return Err(anyhow!(
"Expected value to be between {} and {} but received {}",
min_amount,
max_amount,
amount
));
};
Ok(amount)
}
Err(error) => Err(error),
}
}
Hopefully seeing this full implementation will help someone out there.
Source code.

Is there a compact and idiomatic way to print an error and return without returning the error?

I'm writing a function that will be called in an infinite loop and only execute something when getting well-formed data from a web-service. If the service is down, returns non-json, or returns json we do not understand, the function should just log the error and return (to be called again after a pause).
I found myself copying and pasting something like this:
let v = match v {
Ok(data) => data,
Err(error) => {
println!("Error decoding json: {:?}", error);
return;
}
};
The body of the error matcher would be different each time. Sometimes it's panic, sometimes it has different messages, and sometimes elements of error could be broken down further to form a better message, but the rest of the construct would be the same.
Is there a shorthand for this? I'm aware of the ? syntax, but that's for propagation. I don't feel that propagation will help with the scenario when you need slightly different processing in case of the error like in the scenario described above. This is because the particular differences in handling belong right here, not up the stack.
I have not written a lot of code in Rust yet so it is very likely that I'm missing something obvious.
In C#, the above would look something like this:
if (v == null)
{
Console.WriteLine("Error decoding json!");
return;
}
or
if (error != null)
{
Console.WriteLine($"Error decoding json: {error}");
return;
}
both of which is much less verbose than in Rust.
If I understood the comments below, one way of shortening would be something like this:
if let Err(error) = v {
println!("Error decoding json: {:?}", error);
return;
}
let v = v.unwrap();
This looks more compact, thank you. Is this idiomatic? Would you write it this way?
I don't feel that propagation will help with the scenario when you need slightly different processing in case of the error like in the scenario described above. This is because the particular differences in handling belong right here, not up the stack.
This is something a custom error type can help with. In this case you have a common behavior ("log an error") and you want to do that in slightly different ways for different values. It makes sense to move the "log an error" part up to the caller (let's call the function try_poll):
loop {
if let Err(e) = try_poll() {
println!("{}", e);
}
sleep(100);
}
And create a type that implements Display, and From<E> for each error type E:
enum PollError {
NetworkError(NetworkError),
JsonParseError(JsonParseError),
}
impl fmt::Display for PollError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
PollError::NetworkError(ref e) => write!(f, "Error downloading file: {:?}", e),
PollError::JsonParseError(ref e) => write!(f, "Error parsing JSON: {:?}", e),
}
}
}
impl From<NetworkError> for PollError {
fn from(e: NetworkError) -> Self {
PollError::NetworkError(e)
}
}
impl From<JsonParseError> for PollError {
fn from(e: JsonParseError) -> Self {
PollError::JsonParseError(e)
}
}
Now you can use ? to propagate the error, but the caller still doesn't have to be concerned with which error specifically it is.
fn try_poll() -> Result<(), PollError> {
let data = try_fetch_content()?;
let json = try_parse_json(data)?;
println!("Parsed {:?}", json);
Ok(())
}
(playground)
Ok, I want that, but without all the From implementations.
The tedious part about this is all the impl Froms, which are necessary because of the custom error type. If the only thing that will ever be done with an error is log and ignore it, a custom error type is not particularly useful -- the only thing that really needs to be returned is the error message itself.
In that case, have try_poll instead return Result<(), String>, and use Result::map_err to turn each individual error immediately into an error message, before using ? to propagate it:
fn try_poll() -> Result<(), String> {
let data = try_fetch_content()
.map_err(|e| format!("Error downloading file: {:?}", e))?;
let json = try_parse_json(data)
.map_err(|e| format!("Error parsing JSON: {:?}", e))?;
println!("Parsed {:?}", json);
Ok(())
}
(playground)
The first edition of The Rust Programming Language has this to say about String as an error type:
A rule of thumb is to define your own error type, but a String error type will do in a pinch, particularly if you're writing an application. If you're writing a library, defining your own error type should be strongly preferred so that you don't remove choices from the caller unnecessarily.
As an alternative to a custom macro_rule you could also use ? with Option<T> and a trait extension for Result to print errors and convert successful values.
Playground
pub trait ResultOkPrintErrExt<T> {
fn ok_or_print_err(self, msg: &str) -> Option<T>;
}
impl<T, E> ResultOkPrintErrExt<T> for Result<T, E>
where
E: ::std::fmt::Debug,
{
fn ok_or_print_err(self, msg: &str) -> Option<T> {
match self {
Ok(v) => Some(v),
Err(e) => {
eprintln!("{}: {:?}", msg, e);
None
}
}
}
}
fn read_input() -> Result<u32, ()> {
// Ok(5)
Err(())
}
fn run() -> Option<()> {
let v: u32 = read_input().ok_or_print_err("invalid input")?;
println!("got input: {}", v);
Some(())
}
fn main() {
run();
}

Resources