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.
Related
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(())
}
There is context that can convert an optional value to anyhow::Error which is very convenient.
Simplest way to unwrap an option and return Error if None (Anyhow)
However, how do we test that in unit-tests?
Let's say we have a foo like this:
fn foo(input: i32) -> Result<i32> {
// this only keep odd numbers
let filtered = if input % 2 == 0 { Some(input) } else { None };
filtered.context("Not a valid number")
}
It is easy to test that it is valid output, or that the output is an error. But how do we test the error message from the context?
mod test {
use super::*;
#[test]
fn test_valid_number() -> Result<()> {
let result = foo(4)?;
assert_eq!(result, 4);
Ok(())
}
#[test]
fn test_invalid_number() -> Result<()> {
let result = foo(3);
assert!(result.is_err());
Ok(())
}
// error[E0599]: no method named `message` found for struct `anyhow::Error` in the current scope
// --> src/main.rs:33:40
// |
// 33 | assert_eq!(result.unwrap_err().message(), "Not a valid number");
// | ^^^^^^^ method not found in `anyhow::Error`
#[test]
fn test_invalid_number_error_message() -> Result<()> {
let result = foo(3);
assert_eq!(result.unwrap_err().message(), "Not a valid number");
Ok(())
}
}
You can use .chain() and .root_cause() to deal with levels of context and use .downcast_ref() or format! to handle the specific error. For example, lets say you had 2 levels of context.
use anyhow::*;
fn bar(input: i32) -> Result<i32> {
// this only keep odd numbers
let filtered = if input % 2 == 0 { Some(input) } else { None };
filtered.context("Not a valid number")
}
fn foo(input: i32) -> Result<i32> {
return bar(input).context("Handled by bar")
}
In this example the chain would be of the errors "Handled by bar" -> "Not a valid number".
#[test]
fn check_top_error() -> Result<()> {
let result = foo(3);
let error = result.unwrap_err();
// Check top error or context
assert_eq!(format!("{}", error), "Handled by bar");
// Go down the error chain and inspect each error
let mut chain = error.chain();
assert_eq!(chain.next().map(|x| format!("{x}")), Some("Handled by bar".to_owned()));
assert_eq!(chain.next().map(|x| format!("{x}")), Some("Not a valid number".to_owned()));
assert_eq!(chain.next().map(|x| format!("{x}")), None);
Ok(())
}
#[test]
fn check_root_cause() -> Result<()> {
let result = foo(3);
let error = result.unwrap_err();
// Equivalent to .chain().next_back().unwrap()
let root_cause = error.root_cause();
assert_eq!(format!("{}", root_cause), "Not a valid number");
Ok(())
}
Now, you may have been wondering about my use of format!. It turns out a better solution exists involving downcast_ref, but it requires that your context implement std::error::Error and str does not. Here is an example of this taken directly from the anyhow documentation.
use anyhow::{Context, Result};
fn do_it() -> Result<()> {
helper().context(HelperFailed)?;
...
}
fn main() {
let err = do_it().unwrap_err();
if let Some(e) = err.downcast_ref::<HelperFailed>() {
// If helper failed, this downcast will succeed because
// HelperFailed is the context that has been attached to
// that error.
}
}
As a side note, you may find it easier to use .then() or .then_some() for cases like if input % 2 == 0 { Some(input) } else { None } where you create Some based on a boolean. Simply put, if abc { Some(xyz) } else { None } is equivalent to abc.then(|| xyz). .then_some() passes by value instead of using a closure so I don't usually use it.
In a procedural macro attached to a main function, I am parsing the attributes passed as AttributeArgs to the macro.
#[proc_macro_attribute]
pub fn macro(_meta: CompilerTokenStream, input: CompilerTokenStream) -> CompilerTokenStream {
let attrs = syn::parse_macro_input!(_meta as syn::AttributeArgs);
for nested_meta in attrs {
match nested_meta {
syn::NestedMeta::Meta(m) =>
println!("Parsed metadata: {:?}", &m.path().get_ident().unwrap().to_string()),
syn::NestedMeta::Lit(lit) => match lit {
syn::Lit::Str(ref l) => return report_literals_not_allowed(&l.value(), &lit),
syn::Lit::ByteStr(ref l) => return report_literals_not_allowed(&String::from_utf8_lossy(&l.value()), &lit),
syn::Lit::Byte(ref l) => return report_literals_not_allowed(&l.value().to_string(), &lit),
syn::Lit::Char(ref l) => return report_literals_not_allowed(&l.value().to_string(), &lit),
syn::Lit::Int(ref l) => return report_literals_not_allowed(&l.to_string(), &lit),
syn::Lit::Float(ref l) => return report_literals_not_allowed(&l.to_string(), &lit),
syn::Lit::Bool(ref l) => return report_literals_not_allowed(&l.value().to_string(), &lit),
syn::Lit::Verbatim(ref l) => return report_literals_not_allowed(&l.to_string(), &lit)
}
}
}
// Parses the function that this attribute is attached to
let func_res = syn::parse::<FunctionParser>(input);
if func_res.is_err() {
return quote! { fn main() {} }.into()
}
let func = func_res.ok().unwrap();
let sign = func.clone().sig;
let body = func.clone().block.stmts;
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
Handler::run().await;
});
let mut queries_tokens: Vec<TokenStream> = Vec::new();
wire_queries_to_execute(&mut queries_tokens);
// The final code wired in main()
quote! {
#[tokio::main]
async #sign {
{
#(#queries_tokens)*
}
#(#body)*
}
}.into()
}
When the user of this macro, makes an error inside main, I return this:
let func_res = syn::parse::<FunctionParser>(input);
if func_res.is_err() {
return quote! { fn main() {} }.into()
}
Rust analyzer and the compiler reports fine the errors on the code, and then a new main() is wired again, so there's no error main function not found in crate. Please, consider add a main function... which is the problem that I want to solve.
But, when an error is detected on the parsed attributes, I am not able to get rid out of that error. What I've tried so far is this:
pub fn report_literals_not_allowed(ident: &str, s: &Lit) -> TokenStream {
syn::Error::new_spanned(ident, s.span.into()),
"No literals allowed in the `macro` proc-macro"
).into_compile_error().into()
}
Which reports exactly where the user is passing a literal, which is a feature not allowed in the arguments of the attribute.
I've tried to solve it like this:
pub fn report_literals_not_allowed(ident: &str, s: &Lit) -> TokenStream {
let ident = Ident::new(ident, s.span().into());
syn::Error::new_spanned(
quote! {
#ident
fn main() {}
},
"No literals allowed in the `macro` proc-macro"
).into_compile_error().into()
}
but I still have the same error of no 'main' funcion found in crate...
How can I report the error of that the macro does not accept literal values, and remove
the error of not main found at the same time?
syn::Error just generates code that calls compile_error!(). It is a normal TokenStream. You just need to extend it with the code you want:
pub fn report_literals_not_allowed(ident: &str, s: &Lit) -> TokenStream {
let error = syn::Error::new_spanned(s, "No literals allowed in the `macro` proc-macro")
.into_compile_error();
quote! {
#error
fn main() {}
}
.into()
}
This code walks the /tmp folder to show files that end in .txt:
const FOLDER_NAME: &str = "/tmp";
const PATTERN: &str = ".txt";
use std::error::Error;
use walkdir::WalkDir; // 2.2.9
fn main() -> Result<(), Box<dyn Error>> {
println!("Walking folder {}", FOLDER_NAME);
for entry in WalkDir::new(FOLDER_NAME).into_iter().filter_map(|e| e.ok()) {
let x = entry.file_name().to_str();
match x {
Some(x) if x.contains(PATTERN) => println!("This file matches: {:?}", entry),
_ => (),
}
}
Ok(())
}
Although this works, is it possible to leverage filter_map to do the suffix filtering that's currently happening in match?
You need to return the entry wrapped in a Some when the condition is true:
use std::error::Error;
use walkdir::WalkDir; // 2.2.9
const FOLDER_NAME: &str = "/tmp";
const PATTERN: &str = ".txt";
fn main() -> Result<(), Box<dyn Error>> {
println!("Walking folder {}", FOLDER_NAME);
let valid_entries = WalkDir::new(FOLDER_NAME)
.into_iter()
.flat_map(|e| e)
.flat_map(|e| {
let name = e.file_name().to_str()?;
if name.contains(PATTERN) {
Some(e)
} else {
None
}
});
for entry in valid_entries {
println!("This file matches: {:?}", entry);
}
Ok(())
}
You'll note that I've secretly switched to Iterator::flat_map. Iterator::filter_map would also work, but I find flat_map more ergonomic, especially for your "ignore the errors" case.
It's debatable whether this is useful compared to a regular Iterator::filter call:
let valid_entries = WalkDir::new(FOLDER_NAME)
.into_iter()
.flat_map(|e| e)
.filter(|e| {
e.file_name()
.to_str()
.map_or(false, |n| n.contains(PATTERN))
});
See also:
Why does `Option` support `IntoIterator`?
How can I filter an iterator when the predicate returns a Result<bool, _>?
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 };