Why does this macro invocation result in an unresolved name? - macros

Here is a simplified version of a macro I am trying to implement for an RPC library I am working on:
#[macro_export]
macro_rules! msgpack_rpc {
(
$(
rpc $name:ident ( $( $arg:ident : $arg_ty:ty ),* ) -> $ret_ty:ty | $err_ty:ty;
)+
) => (
pub trait Service {
$(
fn $name ( &self, $( $arg : $arg_ty ),* ) -> Result<$ret_ty, $err_ty>;
)+
}
pub struct Server;
impl Server {
pub fn listen<S>(handle: &(), address: (), service: S)
-> ::std::io::Result<()>
where S: Service + Send + Sync + 'static {
let service = move |msg: &str| {
let result = match msg {
$(
stringify!($name) => {
service.$name($( $arg ),*)
.map(String::from)
.map_err(String::from)
}
),+,
_ => String::from("method not supported".into()),
};
};
Ok(())
}
}
)
}
msgpack_rpc! {
rpc echo(arg: i64) -> i64 | ();
}
The macro expansion fails to compile with this error:
error: unresolved name `arg` [--explain E0425]
--> <anon>:40:17
|>
40 |> rpc echo(arg: i64) -> i64 | ();
|> ^
<anon>:39:1: 41:2: note: in this expansion of msgpack_rpc! (defined in <anon>)
From reading similar questions, I know that macro_rules sometimes has problems expanding statements. However, I am confused as to why it is having trouble expanding items in this case.
Is there a workaround to fix the expansion?

You don't have any variable named arg in the context in which you call the function. This is the "unresolved arg" the compiler is complaining about.
stringify!($name) => {
$( let $arg = Default::default(); )*
service.$name($( $arg ),*)
.map(String::from)
.map_err(String::from)
}

Related

How do I define a macro which defines another macro when the inner macro takes arguments?

Minimal code to reproduce:
macro_rules! test {
($name:ident: $count:expr) => {
macro_rules! $name {
($($v:expr),*) => {}
}
}
}
test!(yo: 123);
Got error:
error: attempted to repeat an expression containing no syntax variables matched as repeating at this depth
--> src/lib.rs:4:15
|
4 | ($($v:expr),*) => {}
| ^^^^^^^^^
Removing $count:expr or changing $count:expr to another type like $count:block omits the error, but I really need it to be expr. What does the error mean?
This is a known issue (#35853). The current recommended workaround is to pass in the dollar sign $ as a separate token. You can then call yourself, passing in the $:
macro_rules! test {
($name:ident: $count:expr) => { test!($name: $count, $) };
($name:ident: $count:expr, $dol:tt) => {
macro_rules! $name {
($dol($v:expr),*) => {}
}
};
}
fn main() {
test!(yo: 2);
yo!(42);
}

Is it possible to let a macro expand to a struct field?

I would like to do the following, but macros in that position don’t seem to work (I get error: expected `:`, found `!`. How can I pattern-match individual struct members and attach attributes to them based on the match?
use serde_derive::Serialize;
macro_rules! optional_param {
($name:ident : Option<$type:ty>) => { #[serde(skip_serializing_if = "Option::is_none")] pub $name: Option<$ty> };
($name:ident : Vec <$type:ty>) => { #[serde(skip_serializing_if = "Vec::is_empty" )] pub $name: Vec <$ty> };
($name:ident : bool ) => { #[serde(skip_serializing_if = "bool::not" )] pub $name: bool };
}
macro_rules! impl_extra {
( $name:ident { $( $param:ident : $type:ty ),* $(,)* } ) => (
#[derive(Default,Debug,Serialize)]
pub struct $name {
$( optional_param!($param : $type), )*
}
);
}
impl_extra!(MyStruct { member: Option<String> });
Link to the playground
Indeed, macro invocations are not valid in the middle of a struct definition. However, we can use metavariables there. The trick is to parse the parameters incrementally, building the tokens for the field definitions along the way, and when there's no more input to process, emit a struct definition with the field definitions coming from a metavariable.
As a first step, let's see what a macro that doesn't handle field types specifically looks like:
macro_rules! impl_extra {
( # $name:ident { } -> ($($result:tt)*) ) => (
#[derive(Default, Debug, Serialize)]
pub struct $name {
$($result)*
}
);
( # $name:ident { $param:ident : $type:ty, $($rest:tt)* } -> ($($result:tt)*) ) => (
impl_extra!(# $name { $($rest)* } -> (
$($result)*
pub $param : $type,
));
);
( $name:ident { $( $param:ident : $type:ty ),* $(,)* } ) => (
impl_extra!(# $name { $($param : $type,)* } -> ());
);
}
The only thing this macro does is add pub on each field and define a pub struct with a #[derive] attribute. The first rule handles the terminal case, i.e. when there are no more fields to process. The second rule handles the recursive case, and the third rule handles the macro's "public" syntax and transforms it into the "processing" syntax.
Note that I'm using an # as the initial token for internal rules to distinguish them from "public" rules. If this macro is not meant to be exported to other crates, then you could also move the internal rules to a different macro. If the macro is exported though, then the separate macro for the internal rules might have to be exported too.
Now, let's handle the various field types:
macro_rules! impl_extra {
( # $name:ident { } -> ($($result:tt)*) ) => (
#[derive(Default, Debug, Serialize)]
pub struct $name {
$($result)*
}
);
( # $name:ident { $param:ident : Option<$type:ty>, $($rest:tt)* } -> ($($result:tt)*) ) => (
impl_extra!(# $name { $($rest)* } -> (
$($result)*
#[serde(skip_serializing_if = "Option::is_none")]
pub $param : Option<$type>,
));
);
( # $name:ident { $param:ident : Vec<$type:ty>, $($rest:tt)* } -> ($($result:tt)*) ) => (
impl_extra!(# $name { $($rest)* } -> (
$($result)*
#[serde(skip_serializing_if = "Vec::is_empty")]
pub $param : Vec<$type>,
));
);
( # $name:ident { $param:ident : bool, $($rest:tt)* } -> ($($result:tt)*) ) => (
impl_extra!(# $name { $($rest)* } -> (
$($result)*
#[serde(skip_serializing_if = "bool::not")]
pub $param : bool,
));
);
( $name:ident { $( $param:ident : $($type:tt)* ),* $(,)* } ) => (
impl_extra!(# $name { $($param : $($type)*,)* } -> ());
);
}
Note that there's a difference in the last rule: instead of matching on a ty, we now match on a sequence of tt. That's because once the macro has parsed a ty, it can't be broken down, so when we make a recursive macro call, a ty cannot possibly match something like Option<$type:ty>.

How to capture by reference in rust macro

I have a macro to generate match arms:
macro_rules! sort_by {
( $query:ident, $sort_by:expr, { $( $name:pat => $column:path,)+ } ) => {
match $sort_by.column {
$(
$name => if $sort_by.descending {
$query = $query.order_by($column.desc());
} else {
$query = $query.order_by($column.asc());
},
)+
}
}
}
and I want to call it like this:
sort_by!(query, sort_by.unwrap_or(Sort::desc("id")), {
"id" => table::id,
"customerName" => table::customer_name,
});
But I'm getting an error:
sort_by!(query, &sort_by.unwrap_or(Sort::desc("id")), {
^^^^^^^ value moved here in previous iteration of loop
So I have to call it like this:
let sort = sort_by.unwrap_or(Sort::desc("id"));
sort_by!(query, &sort, {
"id" => table::id,
"customerName" => table::customer_name,
});
What should I change to be able to use the expression directly in the macro invocation?
Using a macro is equivalent to substituting the code it expands to into its call site. This means if the macro expansion contains $sort_by multiple times, the code will evaluate the expression you pass in as $sort_by multiple times. If the expression consumes some variable, this will be invalid.
This is in contrast to how function calls work. If you pass an expression to a function, it will be evaluated before calling the function, and only the result is passed to the function.
If this is the source of your problem, you can fix it by assigning $sort_by to a local variable inside your macro expansion, and only access the local variable subsequently:
macro_rules! sort_by {
($query:ident, $sort_by:expr, { $($name:pat => $column:path,)+ }) => {
let sort_by = $sort_by;
match sort_by.column {
$(
$name => if sort_by.descending {
$query = $query.order_by($column.desc());
} else {
$query = $query.order_by($column.asc());
},
)+
}
}
}
(Note that I could not test this, since your example is incomplete.)

How to implement the Lispian cond macro?

Intended usage:
cond! {
x > 5 => 0,
x < 3 => 1,
true => -1
}
Should expand to:
if x > 5 { 0 } else if x < 3 { 1 } else if true { -1 }
Note that it doesn't produce a catch-all else { ... } suffix.
My attempt:
macro_rules! cond(
($pred:expr => $body:expr) => {{
if $pred {$body}
}};
($pred:expr => $body:expr, $($preds:expr => $bodies:expr),+) => {{
cond! { $pred => $body } else cond! { $($preds => $bodies),+ }
}};
);
However, the compiler complains about the else keyword.
error: expected expression, found keyword `else`
--> src/main.rs:32:34
|
32 | cond! { $pred => $body } else cond! { $($preds => $bodies),+ }
| ^^^^
Macros in Rust don't perform textual substitution like the C preprocessor does. Moreover, the result of a macro is already "parsed", so you can't just append something after the macro invocation that's supposed to be part of what the macro expands to.
In your case, you can't put an else after the first cond! invocation because the compiler has already finished parsing the if expression; you need to put the if and the else together. Likewise, when you invoke cond! again after the else, you need to add braces around the call, because the sequence else if does not begin a nested if expression.
macro_rules! cond {
($pred:expr => $body:expr) => {
if $pred { $body }
};
($pred:expr => $body:expr, $($preds:expr => $bodies:expr),+) => {
if $pred { $body } else { cond! { $($preds => $bodies),+ } }
};
}
Ultimately though, this macro is pretty much useless. An if expression without an else clause always has its type inferred to be (), so unless all the branches evaluate to () (or diverge), the expanded macro will produce type mismatch errors.

How can I use a macro to create an array of function names starting from a collection of function definitions?

I would like to create an array in a macro to transform something like:
let array = create_array!(
fn test() -> i32 { }
fn test1() { }
);
into
let array = [test, test1];
I tried this:
macro_rules! create_array {
() => {
};
(fn $func_name:ident () -> $return_type:ty $block:block $($rest:tt)*) => {
$func_name,
create_array!($($rest)*);
};
(fn $func_name:ident () $block:block $($rest:tt)*) => {
$func_name,
create_array!($($rest)*);
};
}
but it fails with the following error:
error: macro expansion ignores token `,` and any following
--> src/main.rs:11:19
|
11 | $func_name,
| ^
|
note: caused by the macro expansion here; the usage of `create_array!` is likely invalid in expression context
--> src/main.rs:27:18
|
27 | let array = [create_array!(
|
I also tried this:
macro_rules! create_array {
($(fn $func_name:ident () $( -> $return_type:ty )* $block:block)*) => {
[$($func_name),*]
};
}
but it fails with:
error: local ambiguity: multiple parsing options: built-in NTs block ('block') or 1 other option.
--> src/main.rs:22:19
|
22 | fn test() -> i32 { }
|
So how can I create an array in such a case?
The parsing ambiguity of -> vs $:block has been resolved as of Rust 1.20, so the second version you tried will now work as intended.
macro_rules! create_array {
($(fn $func_name:ident () $(-> $return_type:ty)* $block:block)*) => {
[$($func_name),*]
};
}
fn main() {
let test = "TEST";
let test1 = "TEST1";
let array = create_array! {
fn test() -> i32 {}
fn test1() {}
};
println!("{:?}", array);
}