是否可以使用宏计算 C 风格的“enum”值?

Bla*_*sui 2 macros enums rust

我有一个包含四个字母标识符的巨大列表。我想要每个人都有一个枚举常量。常量的值应该是 ASCII 标识符u32

enum E {
    Elem = my_macro!(Elem),
}
Run Code Online (Sandbox Code Playgroud)

由于宏将简单地被生成的 AST 替换,因此编译器将看到非常量表达式并引发错误。

这是可能的,还是我必须显式生成写出常量?

She*_*ter 5

是的,但请记住,宏实际上只能让您消除苦差事,而不能真正添加​​新功能。

首先编写您想要的不使用宏的代码。这让您看到什么是可能的。例如,我的探索是这样的:

#[repr(u32)]
enum Foo {
    Start = (1 << 24 | 2 << 16 | 3 << 8 | 4) as u32,
    Start2 = ((b'1' as u32) << 24 | 2 << 16 | 3 << 8 | 4) as u32,
    // the index operation on const values is unstable
    //Start3 = ((b"1"[0] as u32) << 24 | 2 << 16 | 3 << 8 | 4) as u32,
}
Run Code Online (Sandbox Code Playgroud)

遗憾的是,我们不能使用最后一种形式,因为常量索引还不是常量表达式。据我所知,我们能做的就是Start2。接下来,重复该模式几次以查看冗余发生的位置:

#[repr(u32)]
enum Foo {
    Start = ((b'S' as u32) << 24 | (b'T' as u32) << 16 | (b'R' as u32) << 8 | (b'T' as u32)) as u32,
    End   = ((b' ' as u32) << 24 | (b'E' as u32) << 16 | (b'N' as u32) << 8 | (b'D' as u32)) as u32,
}
Run Code Online (Sandbox Code Playgroud)

现在您已准备好创建宏:

macro_rules! tagged_ascii_headers {
    (enum $name:ident {
        $($var:ident = $v1:expr, $v2:expr, $v3:expr, $v4:expr,)*
    }) => {
        #[repr(u32)]
        enum $name {
            $($var = (($v1 as u32) << 24 | ($v2 as u32) << 16 | ($v3 as u32) << 8 | $v4 as u32),)*
        }
    }
}

tagged_ascii_headers! {
    enum Foo {
        Start = b'S', b'T', b'R', b'T',
        End   = b' ', b'E', b'N', b'D',
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以使用宏语法来找到看起来不错的东西。我开始着手

macro_rules! tagged_ascii_headers {
    (enum $name:ident {
        $($var:ident = $v1:tt $v2:tt $v3:tt $v4:tt,)*
    }) => {
        #[repr(u32)]
        enum $name {
            $($var = (($v1 as u32) << 24 | ($v2 as u32) << 16 | ($v3 as u32) << 8 | $v4 as u32),)*
        }
    }
}

tagged_ascii_headers! {
    enum Foo {
        Start = 'S' 'T' 'R' 'T',
        End   = ' ' 'E' 'N' 'D',
    }
}
Run Code Online (Sandbox Code Playgroud)

这有点好,但最终您可能需要更持续的评估才能可用。如果数组可以被索引,你可以演变成类似的东西

tagged_ascii_headers! {
    enum Foo {
        Start = b"STRT",
        End   = b" END",
    }
}
Run Code Online (Sandbox Code Playgroud)

由于宏将简单地被生成的 AST 替换

这是真实的

编译器将看到一个非常量表达式

这句话只对了一半。例如,这编译得很好:

macro_rules! foo {
    () => { 42 }
}

enum Foo {
    Start = foo!(),
}
Run Code Online (Sandbox Code Playgroud)

所以实际上,与 const-ness 无关,它与宏扩展为什么有关。


您还可以转到构建脚本:

const THINGS: &'static [(&'static str, &'static [u8; 4])] = &[
    ("Start", b"STRT"),
    ("End",   b" END"),
];

fn main() {
    println!("#[repr(u32)]");
    println!("enum Foo {{");
    for &(name, code) in THINGS {
        let code = (code[0] as u32) << 24 | 
                   (code[1] as u32) << 16 |
                   (code[2] as u32) << 8  |
                    code[3] as u32;
        println!("    {} = {},", name, code);
    }
    println!("}}");
}
Run Code Online (Sandbox Code Playgroud)

您希望将其写入文件而不是标准输出,然后包含从生产代码生成的文件。构建脚本还允许您拥有一些定义所有名称/代码的外部文件(如果有价值的话)。