【问题标题】: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 这是一个人为的例子。我对这个特定的用例并不感兴趣;问题是关于“用宏计数”的一般情况。
  • 好的,在这种情况下,很难知道什么是好的答案,因为在示例中你很明显你会使用迭代。请注意,这个问题的标题与另一个问题非常相似:stackoverflow.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 个元素)。

标签: macros rust rust-macros


【解决方案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) => ();
      }
      

      基于此,我很确定可以发明一组宏来“计数”并在每一步调用各种操作(使用计数)。

      【讨论】:

        猜你喜欢
        • 2023-03-25
        • 1970-01-01
        • 1970-01-01
        • 2016-01-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-07-24
        相关资源
        最近更新 更多