3

In writing Rust code, I frequently want to create my error type in an error block:

match something() { Ok(x) => {...}, Err(e) => Err(MyError::new(format!("Something failed: {}", e))) } 

There is a lot of repeated code in the Err creation that I would like to extract to a function/macro. Part of it is easy:

fn err(msg: String) -> Result<_, MyError> { Err(MyError::new(msg)) } 

However, there is still boilerplate in the error block:

Err(e) => err(format!("Something failed: {}", e)) 

Since I also want to be able to do things like

Err(e) => err(format!("Unable to convert {}: {}", input, e) 

I can't write a single function to get rid of the format!. So clearly I need a macro. I currently have

#[macro_export] macro_rules! foo { ($base: expr, $(arg:expr),*) => {{ Err(MyError::new(format!(base, arg))) }}; } 

I don't get errors with the macro definition, but trying to use it gives me compilation errors:

 | 231 | macro_rules! foo { | ---------------- when calling this macro ... 245 | let xxx = foo!("a", "b"); | ^^^ no rules expected this token in macro call 

The examples that I find online deal with processing the arguments one at a time, such as bar![1,2,3] to push each value onto a vector. I can't figure out what I need to do to get the macro to match and then pass all the arguments to another macro.

1 Answer 1

4

You may be interested in the anyhow crate which contains an anyhow! macro doing something similar.

Your macro is almost good, you're just missing an extra $ in the declaration, and $ prefixes at the usage site.

#[macro_export] macro_rules! foo { ($base: expr, $($args:tt),*) => {{ Err(MyError::new(format!($base, $($args),*))) }}; } 

Note that you can also pass the whole thing to format! without having a separate base parameter.

#[macro_export] macro_rules! foo { ($($args:tt),*) => {{ Err(MyError::new(format!($($args),*))) }}; } 

If you wanted to prefix the error message with another string, format_args! can be used instead of format! to save a memory allocation.

 #[macro_export] macro_rules! foo { ($($args:tt),*) => {{ Err(MyError::new(format!("Something failed: {}", format_args!($($args),*)))) }}; } 
Sign up to request clarification or add additional context in comments.

3 Comments

If MyError is defined in lib.rs, then using $crate::MyError means if you use the macro in another file, you don't need to import MyError
I suggest ($($arg:tt)*) => {{ ... format_args!($($arg)*) ... }} -- without the comma and singular (this is how format! itself is declared
Also, you don't need #[macro_export] most of the time

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.