有没有办法用宏来计数?
Posted
技术标签:
【中文标题】有没有办法用宏来计数?【英文标题】:Is there a way to count with macros? 【发布时间】:2016-02-18 12:29:27 【问题描述】:我想创建一个打印“Hello”指定次数的宏。它的用法如下:
many_greetings!(3); // expands to three `println!("Hello");` statements
创建该宏的简单方法是:
macro_rules! many_greetings
($times:expr) =>
println!("Hello");
many_greetings!($times - 1);
;
(0) => ();
但是,这不起作用,因为编译器不计算表达式; $times - 1
不是计算出来的,而是作为新表达式输入到宏中。
【问题讨论】:
你能得到的最接近的是递归,利用事实匹配被重新评估:is.gd/3QfTr9 虽然它很丑。 如果您能说明为什么在宏中使用for
循环不是一个好的解决方案(因为这似乎是一个显而易见的答案),那就太好了。
@ideasman42 这是一个人为的例子。我对这个特定的用例并不感兴趣;问题是关于“用宏计数”的一般情况。
好的,在这种情况下,很难知道什么是好的答案,因为在示例中你很明显你会使用迭代。请注意,这个问题的标题与另一个问题非常相似:***.com/questions/30152800(我在寻找什么以及为什么我偶然发现了这个页面)。 AFAICS 是关于宏扩展,而不是计数。
@ideasman42 为非Copy
元素(例如,String
)或任何类型的 n > 32 个元素构建一个数组初始化器是一个用例,其中for
是不合适。例如:static FOO: [AtomicUsize; 100] = arr_init![AtomicUsize::new(0); 100];
。 arr_init!
宏应在编译时发出 [AtomicUsize::new(0), AtomicUsize::new(0), ... , AtomicUsize::new(0) ]
(n = 100 个元素)。
【参考方案1】:
对于那些正在寻找方法的人,还有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);
【讨论】:
【参考方案2】:正如其他答案已经说过的那样:不,你不能像这样使用声明性宏 (macro_rules!
)。
但您可以将many_greetings!
示例实现为过程宏。程序宏在前一段时间是稳定的,因此该定义适用于稳定。但是,我们还不能将宏扩展为 stable 上的语句——这就是 #![feature(proc_macro_hygiene)]
的用途。
这看起来代码很多,但大部分代码只是错误处理,所以没那么复杂!
examples/main.rs
#![feature(proc_macro_hygiene)]
use count_proc_macro::many_greetings;
fn main()
many_greetings!(3);
Cargo.toml
[package]
name = "count-proc-macro"
version = "0.1.0"
authors = ["me"]
edition = "2018"
[lib]
proc-macro = true
[dependencies]
quote = "0.6"
src/lib.rs
extern crate proc_macro;
use std::iter;
use proc_macro::Span, TokenStream, TokenTree;
use quote::quote, quote_spanned;
/// Expands into multiple `println!("Hello");` statements. E.g.
/// `many_greetings!(3);` will expand into three `println`s.
#[proc_macro]
pub fn many_greetings(input: TokenStream) -> TokenStream
let tokens = input.into_iter().collect::<Vec<_>>();
// Make sure at least one token is provided.
if tokens.is_empty()
return err(Span::call_site(), "expected integer, found no input");
// Make sure we don't have too many tokens.
if tokens.len() > 1
return err(tokens[1].span(), "unexpected second token");
// Get the number from our token.
let count = match &tokens[0]
TokenTree::Literal(lit) =>
// Unfortunately, `Literal` doesn't have nice methods right now, so
// the easiest way for us to get an integer out of it is to convert
// it into string and parse it again.
if let Ok(count) = lit.to_string().parse::<usize>()
count
else
let msg = format!("expected unsigned integer, found ``", lit);
return err(lit.span(), msg);
other =>
let msg = format!("expected integer literal, found ``", other);
return err(other.span(), msg);
;
// Return multiple `println` statements.
iter::repeat(quote! println!("Hello"); )
.map(TokenStream::from)
.take(count)
.collect()
/// Report an error with the given `span` and message.
fn err(span: Span, msg: impl Into<String>) -> TokenStream
let msg = msg.into();
quote_spanned!(span.into()=>
compile_error!(#msg);
).into()
运行cargo run --example main
会打印三个“Hello”。
【讨论】:
【参考方案3】:虽然普通的宏系统不能让你多次重复宏展开,但是在宏中使用for循环是没有问题的:
macro_rules! many_greetings
($times:expr) =>
for _ in 0..$times
println!("Hello");
;
如果你真的需要重复宏,你必须查看过程宏/compiler plugins(从 1.4 开始不稳定,并且有点难以编写)。
编辑:可能有更好的方法来实现这一点,但我今天在这方面花了足够长的时间,所以就这样吧。 repeat!
,一个实际上多次复制一段代码的宏:
main.rs
#![feature(plugin)]
#![plugin(repeat)]
fn main()
let mut n = 0;
repeat! 4
println!("hello ", n);
n += 1;
;
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);
添加到 Cargo.toml
[lib]
name = "repeat"
plugin = true
请注意,如果我们真的不想进行循环,而是在编译时进行扩展,我们必须做一些事情,比如要求文字数字。毕竟,我们无法在编译时评估引用程序其他部分的变量和函数调用。
【讨论】:
当然,循环总是有效的,但它不需要是宏。无论如何,我的例子很愚蠢。您能否发布一些代码,如何使用编译器插件来做到这一点?如果你有时间……那就太棒了:) 我添加了一个可以多次重复任意代码的过程宏示例。我想不出使用这个确切宏的任何充分理由,但正如您所见,这是可能的。 这个答案还有效吗? Rust 的递归宏能处理这个吗?【参考方案4】:据我所知,没有。宏语言基于模式匹配和变量替换,并且只计算宏。
现在,您可以通过评估来实现计数:这很无聊...见the playpen
macro_rules! many_greetings
(3) =>
println!("Hello");
many_greetings!(2);
;
(2) =>
println!("Hello");
many_greetings!(1);
;
(1) =>
println!("Hello");
many_greetings!(0);
;
(0) => ();
基于此,我很确定可以发明一组宏来“计数”并在每一步调用各种操作(使用计数)。
【讨论】:
以上是关于有没有办法用宏来计数?的主要内容,如果未能解决你的问题,请参考以下文章