有没有办法用宏来计算?

Luk*_*odt 14 macros rust rust-macros

我想创建一个指定次数打印"Hello"的宏.它的使用方式如下:

many_greetings!(3);  // expands to three `println!("Hello");` statements
Run Code Online (Sandbox Code Playgroud)

创建该宏的天真方式是:

macro_rules! many_greetings {
    ($times:expr) => {{
        println!("Hello");
        many_greetings!($times - 1);
    }};
    (0) => ();
}
Run Code Online (Sandbox Code Playgroud)

但是,这不起作用,因为编译器不计算表达式; $times - 1不计算,但作为一个新的表达式输入宏.

Eri*_*aas 7

虽然普通的宏系统不能让你多次重复宏扩展,但在宏中使用for循环没有问题:

macro_rules! many_greetings {
    ($times:expr) => {{
        for _ in 0..$times {
            println!("Hello");
        }
    }};
}
Run Code Online (Sandbox Code Playgroud)

如果你真的需要重复宏,你必须查看过程宏/ 编译器插件(1.4版本不稳定,写起来有点难).

编辑:可能有更好的方法来实现这一点,但今天我已经花了足够长的时间,所以这里.repeat!,一个实际上多次重复代码块的宏:

main.rs

#![feature(plugin)]
#![plugin(repeat)]

fn main() {
    let mut n = 0;
    repeat!{ 4 {
        println!("hello {}", n);
        n += 1;
    }};
}
Run Code Online (Sandbox Code Playgroud)

lib.rs

#![feature(plugin_registrar, rustc_private)]

extern crate syntax;
extern crate rustc;

use syntax::codemap::Span;
use syntax::ast::TokenTree;
use syntax::ext::base::{ExtCtxt, MacResult, MacEager, DummyResult};
use rustc::plugin::Registry;
use syntax::util::small_vector::SmallVector;
use syntax::ast::Lit_;
use std::error::Error;

fn expand_repeat(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box<MacResult + 'static> {
    let mut parser = cx.new_parser_from_tts(tts);
    let times = match parser.parse_lit() {
        Ok(lit) => match lit.node {
            Lit_::LitInt(n, _) => n,
            _ => {
                cx.span_err(lit.span, "Expected literal integer");
                return DummyResult::any(sp);
            }
        },
        Err(e) => {
            cx.span_err(sp, e.description());
            return DummyResult::any(sp);
        }
    };
    let res = parser.parse_block();

    match res {
        Ok(block) => {
            let mut stmts = SmallVector::many(block.stmts.clone());
            for _ in 1..times {
                let rep_stmts = SmallVector::many(block.stmts.clone());
                stmts.push_all(rep_stmts);
            }
            MacEager::stmts(stmts)
        }
        Err(e) => {
            cx.span_err(sp, e.description());
            DummyResult::any(sp)
        }
    }
}

#[plugin_registrar]
pub fn plugin_registrar(reg: &mut Registry) {
    reg.register_macro("repeat", expand_repeat);
}
Run Code Online (Sandbox Code Playgroud)

添加到Cargo.toml

[lib]
name = "repeat"
plugin = true
Run Code Online (Sandbox Code Playgroud)

请注意,如果我们真的不想进行循环,但是在编译时进行扩展,我们必须执行诸如需要文字数字之类的操作.毕竟,我们无法在编译时评估引用程序其他部分的变量和函数调用.


Luk*_*odt 5

正如其他答案已经说过的那样:不,你不能用声明性宏 ( macro_rules!) 来计算


但是您可以将many_greetings!示例实现为过程宏。程序宏在不久前就已经稳定了,因此该定义适用于稳定的。然而,我们还不能将宏扩展为稳定的语句——这就是#![feature(proc_macro_hygiene)]它的用途。

这看起来像很多代码,但大多数代码只是错误处理,所以并没有那么复杂!

examples/main.rs

#![feature(proc_macro_hygiene)]

use count_proc_macro::many_greetings;

fn main() {
    many_greetings!(3);
}
Run Code Online (Sandbox Code Playgroud)

Cargo.toml

[package]
name = "count-proc-macro"
version = "0.1.0"
authors = ["me"]
edition = "2018"

[lib]
proc-macro = true

[dependencies]
quote = "0.6"
Run Code Online (Sandbox Code Playgroud)

src/lib.rs

[package]
name = "count-proc-macro"
version = "0.1.0"
authors = ["me"]
edition = "2018"

[lib]
proc-macro = true

[dependencies]
quote = "0.6"
Run Code Online (Sandbox Code Playgroud)

运行cargo run --example main打印三个“你好”。


nyi*_*ang 5

对于那些寻找方法来做到这一点的人,还有seq_macro crate

它相当容易使用,并且可以与稳定的 Rust 一起开箱即用。

use seq_macro::seq;

macro_rules! many_greetings {
    ($times:literal) => {
        seq!{ N in 0..$times {
            println!("Hello");
        }}
    };
}

fn main() {
    many_greetings!(3);
    many_greetings!(12);
}
Run Code Online (Sandbox Code Playgroud)