競技プログラミングにprocedural macroを持ち込む

## 動機

競技プログラミングではよく $N < 10^6$個程度の値を改行区切りで出力することが要求されます。

```rust
fn main() {
    // ...

    let ans: Vec<i64> = unimplemented!();

    for ans in ans {
        println!("{}", ans);
    }
}
```

しかしRustの`println!`はバッファリングを行わず、毎回フラッシュされます。$10^6$個だと150msくらい持っていかれます。これは致命的ではないものの、あまり気分は良くないでしょう。

[Rustで高速な標準出力 - κeenのHappy Hacκing Blog](https://keens.github.io/blog/2017/10/05/rustdekousokunahyoujunshutsuryoku/) の方法で高速化してみましょう。

```diff
 fn main() {
     // ...

     let ans: Vec<i64> = unimplemented!();
​
+    use std::io::{self, BufWriter, Write as _};
+
+    let stdout = io::stdout();
+    let stdout = &mut BufWriter::new(stdout.lock());
+
     for ans in ans {
-        println!("{}", ans);
+        // 間違えて`println!`を使わないようにする。
+        writeln!(stdout, "{}", ans).unwrap();
     }
+    stdout.flush().unwrap();
 }
```

高速化はできましたが、間違えて`println!`を使ったら意味がありません。`if`や`match`の分岐で両方使おうものなら大惨事です。`println!`を新しく定義してしまいましょう。

```diff
 fn main() {
     // ...

     let ans: Vec<i64> = unimplemented!();

     use std::io::{self, BufWriter, Write as _};

     let stdout = io::stdout();
     let stdout = &mut BufWriter::new(stdout.lock());

     for ans in ans {
-        // 間違えて`println!`を使わないようにする。
-        writeln!(stdout, "{}", ans).unwrap();
+        macro_rules! println(($($tt:tt)*) => (writeln!(stdout, $($tt)*).unwrap()));
+        println!("{}", ans);
     }
     stdout.flush().unwrap();
 }
```

安心して使えるようになりましたが、これを関数や宣言型マクロでまとめようとすると上手くいきません。このような形になってしまいます。

```rust
use std::io::{self, BufWriter, StdoutLock, Write as _};

fn main() {
    // ...

    let ans: Vec<i64> = unimplemented!();

    buffered_print(|stdout| {
        macro_rules! println(($($tt:tt)*) => (writeln!(stdout, $($tt)*).unwrap()));
        for ans in ans {
            println!("{}", ans);
        }
    });
}

fn buffered_print(f: impl FnOnce(&mut BufWriter<StdoutLock<'_>>)) {
    let stdout = io::stdout();
    let stdout = &mut BufWriter::new(stdout.lock());
    f(stdout);
    stdout.flush().unwrap();
}
```

このような問題にぶつかった場合、通常のプログラミングであれば[procedural macro](https://doc.rust-lang.org/reference/procedural-macros.html)に頼りたくなることでしょう。[AtCoderで使えるクレートである](https://github.com/rust-lang-ja/atcoder-rust-resources/wiki/2020-Update#proconio)proconioを使えば一行attributeを足せば高速化ができます。

```diff
+#[proconio::fastout]
 fn main() {
     // ...

     let ans: Vec<i64> = unimplemented!();

     for ans in ans {
         println!("{}", ans);
     }
 }
```

しかしAtCoderやCafeCoder以外ではproconioは使えません。なんとかしてprocedural macroを手元で展開できれば他のサイトでも`#[fastout]`のようなマクロが使えるのですが...

私は[cargo-equip](https://github.com/qryxip/cargo-equip)というRustの競プロライブラリを一つの`.rs`ファイルにバンドルするツールを開発しています。これに条件付きでproc-macroを展開する機能を追加してみました。

## 手法

### syn

procedural macroに使われる[syn](https://docs.rs/syn)クレートと[proc-macro2](https://docs.rs/proc-macro2)クレートは通常のプログラムでも普通に使用できます。

cargo-equipでは「`bin`クレート側でマクロを使うときは`#[macro_use]`で使って下さい」としているのでこの制約をそのまま使いましょう。そうすると展開予定のマクロのパスは一意と考えることができるので、マクロの入出力が再現できれば後はsynクレートで適当にやるだけです。

```rust
#[macro_use]
extern crate my_proc_macros as _;

#[twice]
pub fn hello() {
    println!("Hello!");
}
```

↓

```rust
/*#[macro_rules]
extern crate my_proc_macros as _;*/

/*#[twice]
pub fn hello() {
    println!("Hello!");
}*/
pub fn hello() {
    hello();
    hello();

    fn hello() {
        println!("Hello!");
    }
}
```

### watt

[watt](https://docs.rs/watt)というクレートがあります。procedural macroの実装部分をWASMのライブラリとしてコンパイルしてレポジトリに含めておいて、実行時にこれを実行することにより軽量な`proc_macro`クレートを実現するためのライブラリです。マクロをこれで書いてしまえば楽に展開できそうです。

またcustom build内でWASMのランタイムをコンパイルするために[wattbuild](https://github.com/qryxip/wattbuild)というクレートを作りました。
`python(.exe)`を使いcache directory下でビルドするという雑なもので、ビルド時間も当然増加してしまいますが、数MB単位のバイナリをレポジトリに入れなくて良くなります。

`proc_macro`クレート側のAPIは適当にパースすれば(`macro_rules!`等が使われてない限り)なんとか取れそうではありますが、面倒なので次のような`package.metadata`を要求します。

```toml
[package.metadata.cargo-equip.watt.proc-macro-attribute]
name = { path = "$OUT_DIR/my_macros.wasm", function = "name" }
```

### マクロの実行

あとはcargo-equip側で`.wasm`ファイルをwattで実行するだけですが...

```
     Running `/home/ryo/.rustup/toolchains/1.42.0-x86_64-unknown-linux-gnu/bin/cargo check --message-format json -p 'solve:0.0.0' --bin solve`
    Finished dev [unoptimized + debuginfo] target(s) in 0.18s
    Bundling the code
thread 'main' panicked at 'procedural macro API is used outside of a procedural macro', library/proc_macro/src/bridge/client.rs:331:17
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```

はい。proc-macro2が何のためにあるのかを考えれば当然ですね。しかし困りました。wattを使わずにとなると手間がかかりそうです。

ここで考えました。出力結果を得るだけなら

1. watt
2. WASMのランタイム(のファイルパス)
3. 入力のトークン列

の3つさえあれば良いので[wattbuild](https://github.com/qryxip/wattbuild)のときのように適当なディレクトリ内でコンパイルを走らせればwattを実行できるのではないかと。

[proc\_macroクレートでは普通に何でもできる](https://github.com/ubnt-intrepid/tweet-at-compile-time)ので結果はファイルに出力することにしましょう。あとは`proc_macro`クレートと`bin`クレートに分けます。`proc_macro`クレートは再コンパイルされないようにして`opt-level`を`3`にしておき、`bin`クレート側に2.と3.を入力することでwattの出力を得ます。

<https://github.com/qryxip/cargo-equip/blob/91d3ee648cbce29a844dec05ebd92aa0f1c02311/src/workspace.rs#L97-L286>

### 他のアプローチ

他にまともな手段はいくらでもあると思います。例えばrust-analyzerのマクロを扱う部分等が使えそうです。

またrustcには`--pretty=expanded`というnightlyでしか使えないオプションがあり、それを使った[cargo-expand](https://github.com/dtolnay/cargo-expand)というツールがあります。しかしこれは`std`のマクロも普通に展開されてしまい、直接利用することが禁じられた関数等が出てきてしまいます。

```rust
fn main() {
    println!("Hello, World");
}
```

```console
$ cargo +nightly expand
```

```rust
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::v1::*;
#[macro_use]
extern crate std;
fn main() {
    {
        ::std::io::_print(::core::fmt::Arguments::new_v1(
            &["Hello, World\n"],
            &match () {
                () => [],
            },
        ));
    };
}
```

ただこれ、展開後の形からある程度は元のマクロの形式に戻せそうなんですよね。それができたら楽に使えてなおかつ高速だと思います。

## 試す

[Submission Info #33434 - Library Checker](https://judge.yosupo.jp/submission/33434)

proconio v0.4のものを真似た[fastout](https://github.com/qryxip/competitive-programming-library/tree/master/proc-macros/fastout)というクレートを作りました。次のようにして試すことができます。

```console
$ cargo install cargo-equip cargo-udeps # or downloads from GitHub Releases
```

```console
$ rustup update nightly # for cargo-udeps
```

```toml
[package]
name = "solve"
version = "0.0.0"
edition = "2018"

[dependencies]
qryxip-competitive-fastout = { git = "https://github.com/qryxip/competitive-programming-library" }
```

```console
$ cargo equip --resolve-cfgs --remove comments docs --rustfmt --check --bin "$bin_name"
```

## 競技プログラミングで使えそうなprocedural macro

競技プログラミングにおいても(`bin`クレート側で使う)procedural macroはある程度は役に立つと思います。

@tanakhさんの[argio](https://docs.rs/argio), [comprehension](https://docs.rs/comprehension), [memoize](https://docs.rs/memoise), [interpol](https://docs.rs/interpol)等のようなクレートが既にあります。