通过 Rust 宏自定义文字?

Posted

技术标签:

【中文标题】通过 Rust 宏自定义文字?【英文标题】:Custom literals via Rust macros? 【发布时间】:2020-07-02 12:56:00 【问题描述】:

是否可以在 Rust 中定义一个可以解析自定义文字的宏,例如类似于

的东西
vector!(3x + 15y)

为了澄清,我希望能够尽可能接近上述语法(当然是在可能的范围内)。

【问题讨论】:

您是在询问此特定语法(不,3x 不是有效标记)还是一般情况下(当然,例如见this)? 这个特定的语法(或至少类似的东西);编辑了问题,希望能更清楚。 @trentcl 3x 是一个有效的标记,Rust reference 明确表示允许宏接受带有任意后缀的文字。详情见我的回答。 【参考方案1】:

我将假设“自定义文字”是指“一个常规的 Rust 文字(不包括原始文字),紧跟一个自定义标识符”。这包括:

"str"x,带有自定义后缀 x 的字符串文字 "str" 123x,带有自定义后缀 x 的数字文字 123 b"bytes"x,一个字节文字b"bytes",带有自定义后缀x

如果上面的定义对你来说足够了,那么你很幸运,因为上面确实是 Rust 中所有有效的文字标记,根据the Rust reference:

后缀是紧跟在文字主要部分之后的非原始标识符(没有空格)。

任何带有任何后缀的文字(字符串、整数等)都可以作为标记有效,并且可以传递给宏而不会产生错误。宏本身将决定如何解释这样的标记以及是否产生错误。

但是,被解析为 Rust 代码的文字标记的后缀受到限制。非数字文字标记上的任何后缀都将被拒绝,而数字文字标记仅接受以下列表中的后缀。

所以 Rust 明确地 允许宏支持自定义字符串文字。

现在,您将如何编写这样的宏?您不能使用macro_rules! 编写声明性宏,因为无法通过其简单的模式匹配来检测和操作自定义文字后缀。但是,可以编写一个 procedural macro 来执行此操作。

我不会详细介绍如何编写过程宏,因为在单个 *** 答案中写得太多了。但是,我将给您这个过程宏的示例,它按照您的要求做一些事情,作为起点。它采用给定表达式中的任何自定义整数文字123x123y,并将它们转换为函数调用x_literal(123)y_literal(123)

extern crate proc_macro;

use proc_macro::TokenStream;
use quote::ToTokens;
use syn::
    parse_macro_input, parse_quote,
    visit_mut::self, VisitMut,
    Expr, ExprLit, Lit, LitInt,
;


// actual procedural macro
#[proc_macro]
pub fn vector(input: TokenStream) -> TokenStream 
    let mut input = parse_macro_input!(input as Expr);
    LiteralReplacer.visit_expr_mut(&mut input);
    input.into_token_stream().into()


// "visitor" that visits every node in the syntax tree
// we add our own behavior to replace custom literals with proper Rust code
struct LiteralReplacer;

impl VisitMut for LiteralReplacer 
    fn visit_expr_mut(&mut self, i: &mut Expr) 
        if let Expr::Lit(ExprLit  lit, .. ) = i 
            match lit 
                Lit::Int(lit) => 
                    // get literal suffix
                    let suffix = lit.suffix();
                    // get literal without suffix
                    let lit_nosuffix = LitInt::new(lit.base10_digits(), lit.span());

                    match suffix 
                        // replace literal expression with new expression
                        "x" => *i = parse_quote!  x_literal(#lit_nosuffix) ,
                        "y" => *i = parse_quote!  y_literal(#lit_nosuffix) ,
                        _ => (), // other literal suffix we won't modify
                    
                

                _ => (), // other literal type we won't modify
            
         else 
            // not a literal, use default visitor method
            visit_mut::visit_expr_mut(self, i)
        
    

例如,宏会将vector!(3x + 4y) 转换为x_literal(3) + y_literal(4)

【讨论】:

这很好用!另外,我发现我必须在夜间使用 feature(proc_macro_hygiene),因为程序宏通常不能用作表达式。 @MrMobster 很高兴听到这个消息!您还可以使用proc-macro-hack crate 作为解决方法,在 Rust 稳定版中使用表达式宏。

以上是关于通过 Rust 宏自定义文字?的主要内容,如果未能解决你的问题,请参考以下文章

C 语言使用宏自定义可打印的枚举(enum) 类型

Rust编程语言入门之cargocrates.io

匹配Rust中的Option静态字符串文字[duplicate]

你如何在 Rust 中定义自定义的 `Error` 类型?

「Rust笔记」Rust之自定义宏写法

如何在 Rust 中过滤自定义结构的向量?