Can variables be declared procedurally using Rust macros?

Basically, there are two parts to this question:

  • Can you pass an unknown identifier to a macro in Rust ?

  • Can you concatenate strings to generate new variable names in the Rust macro?

For example, something like:

macro_rules! expand( ($x:ident) => ( let mut x_$x = 0; ) ) 

Call expand! (hi) apparent failure because hi is an unknown identifier; but can you do it somehow?

t. 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 are you saying such a terrible idea. Why would you do this?

I'm glad you asked!

Consider this block of rust:

 { let marker = 0; let borrowed = borrow_with_block_lifetime(data, &marker); unsafe { perform_ffi_call(borrowed); } } 

Now you have a borrowed value with a clearly limited lifetime (marker) that does not use the lifetime of the structure, but we can guarantee the existence of the whole ffi call area; at the same time, we do not encounter unclear errors when a * incorrectly mentioned inside an insecure block, and therefore the compiler does not perceive it as an error, despite the error that occurs inside the safe block.

(see also Why do all my pointers point to the same place with to_c_str () in rust? )

Using a macro that can declare temporary variables for this purpose will greatly ease the problems that I encounter with the compiler. That is why I want to do this.

+6
source share
2 answers

Yes, you can pass an arbitrary identifier to a macro and yes, you can combine 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, since concat_idents!() Is itself a macro, you cannot use this concatenated identifier wherever you could use a simple identifier, only in certain places, as in the example above, and this, in my opinion, is A HUGE drawback. Just yesterday, I tried to write a macro that could remove many patterns in my code, but in the end I could not do this because macros do not support arbitrary placement of concatenated identifiers.

By the way, if I understand your idea correctly, you really don't need identifier concatenations to get unique names. Rust macros, unlike C, are hygienic . This means that all local variable names entered inside the macro will not flow to the area where this macro is called. For example, you can assume that this code will work:

 macro_rules! test { ($body:expr) => ({ let x = 10; $body }) } fn main() { let y = test!(x + 10); println!("{}", y); } 

That is, we create the variable x and put the expression after the declaration. It is natural to think that x in test!(x + 10) refers to this variable declared by the macro, and everything should be fine, but in fact this code will not 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 you only need the uniqueness of the local residents, you can safely do nothing and use any names you want, they will be unique automatically. This is explained in the manual, although I find that the example there is somewhat confusing.

+9
source

In cases where concat_idents does not work (in most cases I would like to use it), changing the task from concatenated identifiers to using namespaces really works.

That is, instead of broken code:

 macro_rules! test { ($x:ident) => ({ struct concat_idents!(hello_, $x) {} enum contact_idents!(hello_, $x) {} }) } 

The user can name the namespace, and then have the names of the presets, as shown below:

 macro_rules! test { ($x:ident) => ({ mod $x { struct HelloStruct {} enum HelloEnum {} } }) } 

You now have a name based on the macro argument. This method is only useful in specific cases.

+1
source

All Articles