是否可以使用Rust宏在程序上声明变量?

Dou*_*oug 12 macros rust

基本上,这个问题有两个部分:

  1. 你可以将未知标识符传递给Rust中的宏吗?

  2. 你能组合字符串在Rust宏中生成新的变量名吗?

例如,类似于:

macro_rules! expand(
  ($x:ident) => (
    let mut x_$x = 0;
  )
)
Run Code Online (Sandbox Code Playgroud)

调用expand!(hi)明显失败,因为hi是一个未知的标识符; 但你能以某种方式这样做吗?

即.在C中的等价物如下:

#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;
}
Run Code Online (Sandbox Code Playgroud)

你为什么这么说,多么可怕的主意.你为什么要这样做?

我很高兴你问!

考虑这个锈块:

{
   let marker = 0;
   let borrowed = borrow_with_block_lifetime(data, &marker); 
   unsafe {
      perform_ffi_call(borrowed);
   }
}
Run Code Online (Sandbox Code Playgroud)

您现在有一个借用的值,其显式有界生命周期(标记)不使用结构生命周期,但我们可以保证存在于整个ffi调用范围内; 同时,我们不会遇到模糊错误,其中*在不安全的块内不安全地取消引用,因此编译器不会将其作为错误捕获,尽管在安全块内部发生错误.

(另请参阅为什么我的所有指针都指向了生锈中带有to_c_str()的相同位置?)

使用可以为此目的声明临时变量的宏将大大减轻我与编译器争吵的麻烦.这就是我想要这样做的原因.

Vla*_*eev 13

是的,您可以将任意标识符传递给宏,是的,您可以使用concat_idents!()宏将标识符连接到新的标识符:

#![feature(concat_idents)]

macro_rules! test {
    ($x:ident) => ({
        let z = concat_idents!(hello_, $x);
        z();
    })
}

fn hello_world() {  }

fn main() {
    test!(world);
}
Run Code Online (Sandbox Code Playgroud)

但是,据我所知,因为concat_idents!()它本身就是一个宏,你不能在任何地方使用这个连接标识符,只能在某些地方使用普通标识符,就像上面的例子一样,在我看来,这是一个巨大的缺点.就在昨天,我尝试编写一个可以在我的代码中删除大量样板的宏,但最终我无法做到,因为宏不支持任意放置连接标识符.

顺便说一句,如果我理解你的想法,你真的不需要连接标识符来获得唯一的名称.与C相反,Rust宏是卫生的.这意味着宏中引入的局部变量的所有名称都不会泄漏到调用此宏的范围.例如,您可以假设此代码可以正常工作:

macro_rules! test {
    ($body:expr) => ({ let x = 10; $body })
}

fn main() {
    let y = test!(x + 10);
    println!("{}", y);
}
Run Code Online (Sandbox Code Playgroud)

也就是说,我们创建一个变量x并在声明后放置一个表达式.那么很自然地认为xin test!(x + 10)指的是宏声明的变量,一切都应该没问题,但实际上这段代码不会编译:

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
Run Code Online (Sandbox Code Playgroud)

因此,如果您需要的只是当地人的独特性,那么您可以安全地做任何事情并使用您想要的任何名称,它们将自动独一无二.宏教程中对此进行了解释,但我发现这个例子有些令人困惑.

  • concat_idents可能会删除,它已经通过功能控制[#13295](https://github.com/mozilla/rust/pull/13295)和[#13294](https://github.com/mozilla/rust/问题/ 13294) (2认同)
  • 我们确实需要类似于C中的##的功能。由于字符串concat非常有限,因此我经常需要编写宏,在其中用户要给我两个名字。 (2认同)
  • 有没有办法生成很多函数,例如“handler_0,handler_1,handler_2,...”,并在下划线后添加“literal”? (2认同)

Luk*_*pin 5

concat_idents不起作用的情况下(大多数情况下我想使用它)将问题从连接标识符更改为使用名称空间确实有效。

也就是说,而不是非工作代码:

macro_rules! test {
    ($x:ident) => ({
        struct concat_idents!(hello_, $x) {}
        enum contact_idents!(hello_, $x) {}
    })
}
Run Code Online (Sandbox Code Playgroud)

用户可以命名命名空间,然后预设名称如下:

macro_rules! test {
    ($x:ident) => ({
        mod $x {
            struct HelloStruct {}
            enum HelloEnum {}
        }
    })
}
Run Code Online (Sandbox Code Playgroud)

现在您有了一个基于宏参数的名称。此技术仅在特定情况下有用。


Bo *_*nes 5

还有https://github.com/dtolnay/paste,它在concat_idents功能不足或无法定位夜间编译器的情况下效果很好。

macro_rules! foo_macro {
    ( $( $name:ident ),+ ) => {
        paste::item! {
            #[test]
            fn [<test_ $name>]() {
                assert! false
            }
        }
    };
}
Run Code Online (Sandbox Code Playgroud)