是否可以使用 "key: value" 语法为 hashmap 文字设置一个宏?

Is it possible to have a macro for a hashmap literal with "key: value" syntax?

这个 maplit crate 允许使用 => 作为分隔符的 hashmap 文字。我相信使用 macro_rules! 来允许 : 分隔符是不可能的,但是可以使用处理令牌流的宏吗?

即使在编译器中也无法向语言添加 { key: value } 样式宏吗?

我怀疑问题是与 : 类型运算符冲突,但我无法构造一个模棱两可的示例,编译器无法决定以哪种方式解释 :

如果您要在 maplit! 宏中将 => 替换为 :,则会出现此错误:

error: `$key:expr` is followed by `:`, which is not allowed for `expr` fragments
 --> src/lib.rs:6:17
  |
6 |     ($($key:expr: $value:expr),*) => {
  |                 ^ not allowed after `expr` fragments
  |
  = note: allowed there are: `=>`, `,` or `;`

具体问题是宏接受任意表达式作为键类型。您仍然可以使用 { key: value } 语法,但前提是 keyident(例如)而不是 expr.

您可以在 Rust 参考中通过示例阅读宏的 Follow-set Ambiguity Restrictions 部分:

The parser used by the macro system is reasonably powerful, but it is limited in order to prevent ambiguity in current or future versions of the language.

[...]

expr and stmt may only be followed by one of: =>, ,, or ;.

这并不是说它一定是模棱两可的,尽管在具有很多 :(例如 MyEnum::Variant: ::std::collections::Vec::new())的示例中解析可能会让人感到困惑。之所以这样设计,是因为只有记录的标记 保证 指示表达式的结尾,而其他标记 将来可能会变得 不明确。

不过您会看到,这只会限制 macro_rules! 模式的处理方式,不会影响程序宏,因为您可以自由决定如何解析标记。


我尝试使用 syn 创建一个简单的过程宏来帮助解析表达式,但它在 Expr : Expr 语法上阻塞,因为它试图急切地将第一个表达式解析为 type ascription expression which is a yet-to-be-completed feature.这是 macro_rules! 防范的未来扩展。

如果你真的想要 : 而不是 =>,那么在 中使用标记树并使用括号来表示歧义是可行的方法。这仍然可以通过过程宏来完成,但它不会那么容易获得,而且如果类型归属表达式被添加到语言中,它会变得模棱两可。

@kmdreko 很好地解释了为什么不能在宏中使用表达式后跟冒号。但是,您可以解决这个问题,方法是将密钥强制为令牌树而不是表达式:

macro_rules! hashmap{
    ( $($key:tt : $val:expr),* $(,)? ) =>{{
        #[allow(unused_mut)]
        let mut map = ::std::collections::HashMap::with_capacity(hashmap!(@count $($key),* ));
        $(
            #[allow(unused_parens)]
            let _ = map.insert($key, $val);
        )*
        map
    }};
    (@replace $_t:tt $e:expr ) => { $e };
    (@count $($t:tt)*) => { <[()]>::len(&[$( hashmap!(@replace $t ()) ),*]) }
}

playground

与可能可以很好地处理这种模式的过程宏相反,采用这种方法的缺点是大多数复杂的表达式都必须用圆括号或大括号括起来。例如,

let map = hashmap!{ 2 + 2 : "foo" }; 

行不通,但是

let map = hashmap!{ (2 + 2) : "foo" };

会。