Basically, there are two parts to this question:
Can you pass an unknown identifier to a macro in Rust?
Can you combine strings to generate new variable names in a Rust macro?
For example, something like:
macro_rules! expand( ($x:ident) => ( let mut x_$x = 0; ) )
Calling expand!(hi) obvious fails because hi is an unknown identifier; but can you somehow do this?
ie. The equivalent in C of something like:
#include <stdio.h> #define FN(Name, base) \ int x1_##Name = 0 + base; \ int x2_##Name = 2 + base; \ int x3_##Name = 4 + base; \ int x4_##Name = 8 + base; \ int x5_##Name = 16 + base; int main() { FN(hello, 10) printf("%d %d %d %d %d\n", x1_hello, x2_hello, x3_hello, x4_hello, x5_hello); return 0; }
Why you say, what a terrible idea. Why would you ever want to do that?
I'm glad you asked!
Consider this rust block:
{ let marker = 0; let borrowed = borrow_with_block_lifetime(data, &marker); unsafe { perform_ffi_call(borrowed); } }
You now have a borrowed value with an explicitly bounded lifetime (marker) that isn't using a structure lifetime, but that we can guarantee exists for the entire scope of the ffi call; at the same time we don't run into obscure errors where a *
is de-referenced unsafely inside an unsafe block and so the compiler doesn't catch it as an error, despite the error being made inside a safe block.
(see also Why are all my pointers pointing to the same place with to_c_str() in rust?)
The use a macro that can declare temporary variables for this purpose would considerably ease the troubles I have fighting with the compiler. That's why I want to do this.
Rust has excellent support for macros. Macros enable you to write code that writes other code, which is known as metaprogramming. Macros provide functionality similar to functions but without the runtime cost. There is some compile-time cost, however, since macros are expanded during compile time.
Procedural macros allow you to run code at compile time that operates over Rust syntax, both consuming and producing Rust syntax. You can sort of think of procedural macros as functions from an AST to another AST. Procedural macros must be defined in a crate with the crate type of proc-macro .
Yes however this is only available as a nightly-only experimental API which may be removed.
You can pass arbitrary identifier into a macro and yes, you can concatenate identifiers into a new identifier using concat_idents!()
macro:
#![feature(concat_idents)] macro_rules! test { ($x:ident) => ({ let z = concat_idents!(hello_, $x); z(); }) } fn hello_world() { } fn main() { test!(world); }
However, as far as I know, because concat_idents!()
itself is a macro, you can't use this concatenated identifier everywhere you could use plain identifier, only in certain places like in example above, and this, in my opinion, is a HUGE drawback. Just yesterday I tried to write a macro which could remove a lot of boilerplate in my code, but eventually I was not able to do it because macros do not support arbitrary placement of concatenated identifiers.
BTW, if I understand your idea correctly, you don't really need concatenating identifiers to obtain unique names. Rust macros, contrary to the C ones, are hygienic. This means that all names of local variables introduced inside a macro won't leak to the scope where this macro is called. For example, you could assume that this code would work:
macro_rules! test { ($body:expr) => ({ let x = 10; $body }) } fn main() { let y = test!(x + 10); println!("{}", y); }
That is, we create a variable x
and put an expression after its declaration. It is then natural to think that x
in test!(x + 10)
refers to that variable declared by the macro, and everything should be fine, but in fact this code won't compile:
main3.rs:8:19: 8:20 error: unresolved name `x`. main3.rs:8 let y = test!(x + 10); ^ main3.rs:3:1: 5:2 note: in expansion of test! main3.rs:8:13: 8:27 note: expansion site error: aborting due to previous error
So if all you need is uniqueness of locals, then you can safely do nothing and use any names you want, they will be unique automatically. This is explained in macro tutorial, though I find the example there somewhat confusing.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With