I'm trying to write a macro that will expand this:
let res = log_request_response!(client.my_call("friend".to_string(), 5));
Into this:
let res = {
debug!("Request: {}", args_separated_by_commas);
let res = client.my_call("friend".to_string(), 5);
debug!("Response: {}", res);
res
};
My attempt so far is something like this:
#[macro_export]
macro_rules! log_request_response_to_scuba {
($($client:ident)?.$call:ident($($arg:expr),*);) => {
let mut s = String::new();
$(
{
s.push_str(&format!("{:?}, ", $arg));
}
)*
s.truncate(s.len() - 2);
debug!("Request: {}", s);
// Somehow reconstruct the entire thing with res = at the start.
debug!("Response: {}", res);
res
};
}
But this fails to compile:
error: macro expansion ignores token `{` and any following
--> src/main.rs:10:13
|
10 | {
| ^
...
39 | let res = log_request_response_to_scuba!(client.my_call(hey, 5));
| ------------------------------------------------------ caused by the macro expansion here
|
= note: the usage of `log_request_response_to_scuba!` is likely invalid in expression context
If I remove the .
in between the client
and call
match it throws a different error about an ambiguous match (which makes sense).
So my first nitty gritty question is how do I match a dot? To me this match looks correct but apparently not.
Beyond that any help with making a macro that does what I want would be great. If it were a regex I'd just want this:
.*\((.*)\).*
Where I just capture the stuff inside the parentheses and split them. Then I use the 0th capture group to get the whole thing.
Thanks!
The error message is not because you are matching the dot somehow wrong, but because you are not returning an expression. You want to return this:
{
debug!("Request: {}", args_separated_by_commas);
let res = client.my_call("friend".to_string(), 5);
debug!("Response: {}", res);
res
};
However, your macro currently returns something more akin to this:
debug!("Request: {}", args_separated_by_commas);
let res = client.my_call("friend".to_string(), 5);
debug!("Response: {}", res);
res
Note the missing curly braces. This can be remedied quite easily by enclosung the complete transcriber part in braces.
I am not sure why client
is optional in your matcher. I assume that you want to optionally allow the user of the macro to either call a function or a method on some variable. Is that correct? If yes, then your code currently does not allow that – it matches client.my_call(...)
as well as .some_function(...)
, but NOT some_function(...)
(note the removed space from the beginning). To do what you want, you could match on $variable:ident$(.$field:ident)?
– note that the dot is here optional as well – or even better $variable:ident$(.$field:ident)*
to allow to call a method on a field of a field of a loval variable (so, something like variable.sub_struct.do_something()
.
The resulting code with some examples:
macro_rules! log_request_response {
($variable:ident$(.$field:ident)*($($arg:expr),*)) => {
{
let mut s = String::new();
$(
{
s.push_str(&format!("{:?}, ", $arg));
}
)*
s.truncate(s.len() - 2);
// using println! here because I don't want to set up logging infrastructure
println!("Request: {}", s);
let res = $variable$(.$field)*($($arg),*);
println!("Response: {}", res);
res
}
};
}
fn test_func(_: String, i: i32) -> i32 {
i
}
struct TestStruct;
impl TestStruct {
fn test_method(&self, _: String, i: i32) -> i32 {
i
}
}
fn main() {
let _ = log_request_response!(TestStruct.test_method("friend".to_string(), 5));
let _ = log_request_response!(test_func("friend".to_string(), 5));
}