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

时间:2014-04-14 13:41:23

标签: macros rust

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

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

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

  3. 例如:

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

    调用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;
    }
    

    为什么你说,这是一个多么可怕的想法。你为什么要这么做?

    我很高兴你问过!

    考虑这个生锈块:

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

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

    (另见Why are all my pointers pointing to the same place with to_c_str() in rust?

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

4 个答案:

答案 0 :(得分:12)

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

#![feature(concat_idents)]

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

fn hello_world() {  }

fn main() {
    test!(world);
}

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

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

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

fn main() {
    let y = test!(x + 10);
    println!("{}", y);
}

也就是说,我们创建一个变量x并在声明后放置一个表达式。很自然地认为x中的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

因此,如果您需要的只是本地人的唯一性,那么您可以安全地做任何事情并使用您想要的任何名称,它们将自动独一无二。这是宏教程中的explained,但我觉得这个例子有些令人困惑。

答案 1 :(得分:3)

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

即代替非工作代码:

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

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

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

现在你有一个基于宏参数的名字。这种技术仅在特定情况下有用。

答案 2 :(得分:3)

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

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

答案 3 :(得分:1)

如果不想使用夜间和外部包装箱,并且标识符是类型,则可以将标识符收集到结构中。

use std::fmt::Debug;

fn print_f<T: Debug>(v: &T){
    println!("{:?}", v);
}

macro_rules! print_all {
    ($($name:ident),+) => {
        struct Values{
            $($name: $name),+
        }
        let values = Values{
            $(
                $name: $name::default()
            ),+
        };
        $(
            print_f(&values.$name);
        )+
    };
}

fn main(){
    print_all!(String, i32, usize);
}

此代码打印

""
0
0

如果您担心Value与某些类型名称冲突,则可以使用一些长UUID作为名称的一部分:

struct Values_110cf51d7a694c808e6fe79bf1485d5b{
   $($name:$name),+
}