lnshi
lnshi

Reputation: 2868

How can i return a formatted string from a rust macro

I am trying to achieve something like this:

  1. Construct an error message with some captured variables;
  2. Log this error message;
  3. Save and return this error message to user eventually;

Apparently this can be done in a few lines of code, but it is a bit tedious since i need this pattern heavily everywhere in my project, i mean like below:

let var_a = 233;
let err = format!("some custom error message, var_a: {}", var_a);

// Here i do the logging with the `tokio-rs/tracing` macros.
error!(err = %err, "some custom error message");

// The s_errs will be eventually showed to end users.
s_errs.append(err);

I am trying to achieve this with a macro which wraps the tokio-rs/tracing macros but return the constructed err message:

#[macro_export]
macro_rules! log_usr_err {
    ($($arg:tt)+) => (
      error!($($arg)+)
      format!($($arg)+)
  );
}

so i can just use this macro in my project to simplify the coding a bit:

let err = log_usr_err!("some custom error message, {}", var_a = var_a);

// The s_errs will be eventually showed to end users.
s_errs.append(err);

but stuck at the compile error:

error: macro expansion ignores token `format` and any following

How should i return the formatted string from my log_urs_err! macro?

Coz in the future in want to include the accurate file! info and the line! info also in my logs, thus i think via macro is the right direction to go, or there is some better approach?

Upvotes: 0

Views: 847

Answers (1)

Cerberus
Cerberus

Reputation: 10218

Let's just try and look at what the expanded code would look like in your case. Reiterating the code in question:

#[macro_export]
macro_rules! log_usr_err {
    ($($arg:tt)+) => (
      error!($($arg)+)
      format!($($arg)+)
  );
}

let err = log_usr_err!("some custom error message, {}", var_a = var_a);

If we try to literally substitute the result of macro expansion, we'd get the following:

let err = error!("some custom error message, {}", var_a = var_a)
format!("some custom error message, {}", var_a = var_a);

This is obviously not valid Rust.

The problem is that your macro is currently written to expand to the list of statements, i.e. to some self-contained part of the code (of course, after adding the necessary semicolon between error and format). It can be, for example, a function body (playground):

macro_rules! log_usr_err {
    ($($arg:tt)+) => (
      error!($($arg)+);
      format!($($arg)+)
  );
}

fn err() {
    log_usr_err!("test");
}

But you'd like to use this macro in expression position, i.e. to assign the value returned from it to the variable. For this, macro must expand to the expression, not to statements.

So, what to do? The change is, in fact, quite simple: a set of statements can be converted to expression by wrapping them in a block, since blocks are expressions:

macro_rules! log_usr_err {
    ($($arg:tt)+) => ({
        error!($($arg)+);
        format!($($arg)+)
    });
}

Note the extra braces around the macro content. With this change, your code compiles.

Upvotes: 2

Related Questions