Why can't macros see variables in scope? [duplicate] - rust

Assuming I want to make the following macro work:
macro_rules! process_numbers {
($name:ident, $process:expr) => {
let $name: Vec<_> = vec![0, 1, 2].iter().map(|num| {
println!("{}", path); // dummy preprocessing
let foo = 3; // some other preprocessing involving side-effects
$process
}).collect();
}
}
process_numbers!(name, {
num + foo
});
Is there a way so that I could access num and foo from within $process?

Rust macros are hygienic. This means that identifiers defined within a macro's body won't leak out of it.
One way to solve this is to modify the macro such that it receives the identifiers as parameters.
macro_rules! process_numbers {
($name:ident, |$num:ident, $foo: ident| $process:expr) => {
let $name: Vec<_> = vec![0, 1, 2].iter().map(|$num| {
println!("{}", path); // dummy preprocessing
let $foo = 3; // some other preprocessing involving side-effects
$process
}).collect();
}
}
process_numbers!(name, |num, foo| {
num + foo
});
Here, I'm using syntax that looks like a closure, which suggests that |num, foo| declare parameters (they actually declare variables – close enough!).
Another way would be to make the $process argument a literal closure (or any invokable expression) that the macro would invoke, passing num and foo as arguments.
macro_rules! process_numbers {
($name:ident, $process:expr) => {
let $name: Vec<_> = vec![0, 1, 2].iter().map(|num| {
println!("{}", path); // dummy preprocessing
let foo = 3; // some other preprocessing involving side-effects
$process(num, foo)
}).collect();
}
}
process_numbers!(name, |num, foo| {
num + foo
});

Related

How to use compile time constants to generate a function name in Rust? [duplicate]

Given the macro matching example, this shows how macros can match an argument.
I've made very minor changes here to use numbers:
macro_rules! foo {
(0 => $e:expr) => (println!("mode X: {}", $e));
(1 => $e:expr) => (println!("mode Y: {}", $e));
}
fn main() {
foo!(1 => 3);
}
Works, printing: mode Y: 3
However I would like to use a constant as an argument, can this be made to work:
const CONST: usize = 1;
macro_rules! foo {
(0 => $e:expr) => (println!("mode X: {}", $e));
(1 => $e:expr) => (println!("mode Y: {}", $e));
}
fn main() {
foo!(CONST => 3);
}
Is this possible in Rust?
Note, using a regular match statement isn't usable for me, since in my code each branch resolves to different types, giving an error.
So I'm specifically interested to know if a constant can be passed to a macro.
No.
Macros operate on the Abstract Syntax Tree, so they reason at the syntactic level: they reason about tokens and their spelling.
For example:
fn main() {
let v = 3;
}
In this case, the AST will look something like:
fn main
\_ let-binding v
\_ literal 3
If you ask a macro whether v is 3, it will look at you funny, and wonder why you would try comparing a variable name and a literal.
I'm fairly sure the answer is "no"; at macro expansion time all you have are token trees - expansion happens before evaluation, or even type inference/checking.
const CONST: usize = 0;
macro_rules! foo {
($i:ident => $e:expr) => {
if $i == 0 {
println!("mode X: {}", $e);
} else if $i == 1 {
println!("mode Y: {}", $e);
}
};
}
fn main() {
foo!(CONST => 3);
}
If you want use identifier in macro it needs to be ident tag and you can use if, else if blocks instead of match.

Is there a way to access variables defined in rust macros from a passed expr?

Assuming I want to make the following macro work:
macro_rules! process_numbers {
($name:ident, $process:expr) => {
let $name: Vec<_> = vec![0, 1, 2].iter().map(|num| {
println!("{}", path); // dummy preprocessing
let foo = 3; // some other preprocessing involving side-effects
$process
}).collect();
}
}
process_numbers!(name, {
num + foo
});
Is there a way so that I could access num and foo from within $process?
Rust macros are hygienic. This means that identifiers defined within a macro's body won't leak out of it.
One way to solve this is to modify the macro such that it receives the identifiers as parameters.
macro_rules! process_numbers {
($name:ident, |$num:ident, $foo: ident| $process:expr) => {
let $name: Vec<_> = vec![0, 1, 2].iter().map(|$num| {
println!("{}", path); // dummy preprocessing
let $foo = 3; // some other preprocessing involving side-effects
$process
}).collect();
}
}
process_numbers!(name, |num, foo| {
num + foo
});
Here, I'm using syntax that looks like a closure, which suggests that |num, foo| declare parameters (they actually declare variables – close enough!).
Another way would be to make the $process argument a literal closure (or any invokable expression) that the macro would invoke, passing num and foo as arguments.
macro_rules! process_numbers {
($name:ident, $process:expr) => {
let $name: Vec<_> = vec![0, 1, 2].iter().map(|num| {
println!("{}", path); // dummy preprocessing
let foo = 3; // some other preprocessing involving side-effects
$process(num, foo)
}).collect();
}
}
process_numbers!(name, |num, foo| {
num + foo
});

Why can Rust not use const as literals? [duplicate]

Given the macro matching example, this shows how macros can match an argument.
I've made very minor changes here to use numbers:
macro_rules! foo {
(0 => $e:expr) => (println!("mode X: {}", $e));
(1 => $e:expr) => (println!("mode Y: {}", $e));
}
fn main() {
foo!(1 => 3);
}
Works, printing: mode Y: 3
However I would like to use a constant as an argument, can this be made to work:
const CONST: usize = 1;
macro_rules! foo {
(0 => $e:expr) => (println!("mode X: {}", $e));
(1 => $e:expr) => (println!("mode Y: {}", $e));
}
fn main() {
foo!(CONST => 3);
}
Is this possible in Rust?
Note, using a regular match statement isn't usable for me, since in my code each branch resolves to different types, giving an error.
So I'm specifically interested to know if a constant can be passed to a macro.
No.
Macros operate on the Abstract Syntax Tree, so they reason at the syntactic level: they reason about tokens and their spelling.
For example:
fn main() {
let v = 3;
}
In this case, the AST will look something like:
fn main
\_ let-binding v
\_ literal 3
If you ask a macro whether v is 3, it will look at you funny, and wonder why you would try comparing a variable name and a literal.
I'm fairly sure the answer is "no"; at macro expansion time all you have are token trees - expansion happens before evaluation, or even type inference/checking.
const CONST: usize = 0;
macro_rules! foo {
($i:ident => $e:expr) => {
if $i == 0 {
println!("mode X: {}", $e);
} else if $i == 1 {
println!("mode Y: {}", $e);
}
};
}
fn main() {
foo!(CONST => 3);
}
If you want use identifier in macro it needs to be ident tag and you can use if, else if blocks instead of match.

How to use a proc macro to create a macro that can violate hygiene?

I am trying to create a simple library called derive_pattern. My ultimate goal is to be able to write something like this:
#[derive(Pattern)]
struct TestStruct {
x: i32,
y: i32,
}
#[test]
fn it_works() {
let test = TestStruct { x: 5, y: 10 };
match test {
// this macro should have been created by the derive,
// and should expand to a pattern that includes all of the field names
test_struct_pattern!() => {
// in this scope x and y should be defined because the pattern bound them
assert!(x == 5);
assert!(y == 5);
}
_ => unreachable!("pattern should have matched"),
}
}
I am curious whether or not it is possible. So far my attempts have failed. When defining a procedural macro it is possible to control the span of identifiers in order to bypass hygiene, but as far as I can tell there is no way for a procedural macro to define a new procedural macro. So instead my procedural macro has to generate macro_rules macros, and those end up enforcing hygiene.
Here is my procedural macro definition:
use inflector::Inflector;
use proc_macro2::Span;
use quote::quote;
use syn::{Fields, FieldsNamed, Ident, ItemStruct};
#[proc_macro_derive(Pattern)]
pub fn derive_pattern(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = proc_macro2::TokenStream::from(input);
let structure: ItemStruct = syn::parse2(input).expect("derive(Pattern) only works on structs.");
let name = structure.ident;
let macro_name = Ident::new(
&(name.to_string().to_snake_case() + "_pattern"),
name.span(),
);
match &structure.fields {
Fields::Named(FieldsNamed {
brace_token: _,
named,
}) => {
let mut field_names: Vec<Ident> = named
.iter()
.map(|f| f.ident.as_ref().unwrap().clone())
.collect();
for field_name in &mut field_names {
field_name.set_span(Span::call_site()); // try (and fail) to bypass hygiene
}
let output = quote! {
macro_rules! #macro_name {
() => {
#name{ #(#field_names),* }
}
}
};
output.into()
}
_ => panic!("derive(Pattern) only supports structs with named fields"),
}
}
I have uploaded a version with full Cargo.toml, etc to github: https://github.com/jgarvin/derive_pattern
The error I get is on the first assert in the test:
Compiling derive_pattern_test_cases v0.1.0 (/home/prophet/derive_pattern/derive_pattern_test_cases)
error[E0425]: cannot find value `x` in this scope
--> derive_pattern_test_cases/src/lib.rs:16:25
|
16 | assert!(x == 5);
| ^ not found in this scope
error[E0425]: cannot find value `y` in this scope
--> derive_pattern_test_cases/src/lib.rs:17:25
|
17 | assert!(y == 5);
| ^ not found in this scope

Rust macro error: local ambiguity: multiple parsing options

The following rust code does not compile because of the macro error
error: local ambiguity: multiple parsing options: built-in NTs stmt ('s') or 1 other option.
macro A is fine. Macro B shows the error.
macro_rules! A {
($x: ident, $($s: stmt)*) => {
println!("hello");
};
}
macro_rules! B {
($x: ident, $($s: stmt)*; $e: expr) => {
println!("hello");
};
}
fn main() {
A![my_name, let x=5];
B![my_name, let x=5; 5];
}
This minimal reproducible example in B is exactly what I need. I want the macro to accept multiple let statements and terminate by some other expression.
What is the ambiguity that is being referred to?
Is there a way around it?
Of those tokens accepted after statement fragments I have tried several combinations yet none appear to make a difference. Neither does swapping the statement with a token tree.
Expressions are statements, so $($s: stmt)*; $e: expr is ambiguous because the compiler can't decide between using s or e when it encounters one.
Since you only expect bindings, you can easily expand them yourself:
macro_rules! A {
($x: ident, $($s: stmt)*) => {
println!("hello");
};
}
macro_rules! B {
($x: ident, $(let $p:pat = $v:expr)*; $e: expr) => {
$(let $p = $v);*
println!("hello: {}", $e);
};
}
fn main() {
A![my_name, let x=5];
B![my_name, let x=5; x+2];
}
Note that this doesn't support including types in the binding (let a: i32 = 42;) because pat can't be followed by :.

Resources