Rustの便利マクロ特集


概要

Rustの標準マクロはかゆいとこに手が届く!
Rustのマクロはいいぞ!
――というわけで。Rustで標準で用意されているマクロでも紹介したいと思いまする。

Rust標準マクロのソースコード

Rustの標準マクロは一つのファイルに全部まとめられている。
ドキュメントから見れます
https://doc.rust-lang.org/src/std/macros.rs.html
一部のマクロはコンパイラマジックだったりして、見てるだけでけっこう面白い。

Rust便利マクロ一覧

早速やっていきましょう。
あ、面白くないのは飛ばすね。

compile_error!

panic!は実行時にエラーを出すけど、こいつはコンパイル時にエラーを出すことができるマクロだ。
コンパイル時にコードに ”compile_error!(なんか文字列);” が含まれていると即座にコンパイルが失敗するよ。
自作マクロで好ましくない引数を渡されたときにエラーを出させたりとかできる。

適当に例を書いた

macro_rules! foo {
    () => {
        compile_error!("式を渡してね!");
    };
    ($e:expr) => {};
}

foo!();

これを実行するとfoo!();の部分がcompile_error!("式を渡してね!");に置き換わり、コンパイルエラーとなる。

エラー内容はこんな感じ

error: 式を渡してね!
  --> src\hoge.rs:13:9
   |
13 |         compile_error!("式を渡してね!");
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
18 | foo!();
   | ------- in this macro invocation

env!

env!は環境変数とかの値を取得するためのマクロだ。
実行時にではなく、コンパイル時に値が置き換えられる点に注意。
型は文字列リテラルだと思います。

使い方

env!("PATH");
env!("SystemRoot");

あと、こいつを使えばCargoが定義している環境変数も取得できる。
ここでどんな値があるか書いてる。
https://doc.rust-lang.org/cargo/reference/environment-variables.html
例えば、cargo.exeの場所が知りたい時はenv!("CARGO")とすると、
~~/cargo.exeみたいにフルパスで取得できる。
Cargoで定義されている環境変数は自作ライブラリのbuildscript書くときとかに重宝する。

option_env!

先程のenv!には少し問題があって、env!("存在しない環境変数名")みたいに無い環境変数を指定するとコンパイルエラーになる。
もし、環境変数がなかったら、こちらの処理に分岐する。っといった処理ができないのです。
そこでoption_env!です。
こいつはOption<&'static str>型で値を返します。
つまり、環境変数が見つからなくても、コンパイルエラーにならずに、Option::Noneを返すだけとなります。便利。

concat_idents!

これは、識別子を結合します。
識別子というのがポイントです!文字列を結合するわけではありません!

公式から引っ張ってきた例です。

fn foobar() -> u32 { 23 }

let f = concat_idents!(foo, bar);
println!("{}", f());

concat_idents!(foo,bar)はコンパイル時にfoobarに置き換えられているのがわかりますね。
concat_idents!(foo,bar,baz)のように複数繋げれます。
残念ながら現在はnightly版のRustでしか動かないようです。

concat!

これはコンパイル時に渡された値を結合して文字列に置き換えるマクロです

//foo10truebarに置き換わる!
concat!("foo", 10, true, "bar");

文字列以外も結合対象にできます。面白いですね。
どうやら、リテラルなら基本結合できそうです。

line!

このマクロは、展開されると、そのマクロが位置している行番号へ置き換わります。
型は符号なし整数リテラルです。

fn main(){
  let hoge:u32 = line!(); //hoge=2

  let huga:u32 = line!(); //huga=4
}

column!

これはさっきのline!の列番号バージョンです

file!

これは、このマクロが配置されているファイルのパスに文字列リテラルで置き換わります。
フルパスではなく、プロジェクトからの相対パスっぽい。

stringify!

これは良いものです。痒いところに手が届く!
まあそれは置いといて。
このマクロは任意の何かをそのままで文字列にしてしまうやつです。

stringify!(1+1);     // "1+1"
stringify!(foobar);  // "foobar"
stringify!(file!()); // "file ! (  )"

// "fn main (  ) { println ! ( "{}" , 1 + 2 ) }"
stringify!(fn main(){println!("{}",1+2 )})

include_str!

これもなかなかユニークで面白い。
コンパイル時にファイルに書かれている文字列に置き換えてくれます。

事前に適当にファイルを用意します。

hoge.txt
abcd
efgh
マクロサイコー!

そしてこうです!

const s: &str = include_str!("hoge.txt");

fn main() {
    println!("{}", s);
}

これで、hoge.txtが出力されます。
自分自身のソースコードを出力とかもできる。

include_bytes!

include_str!のbytes版です。

module_path!

現在位置しているモジュールまでのモジュールパスをコンパイル時に文字列に展開します。

mod foo{
    pub mod bar{
        pub fn baz(){
            println!(module_path!());    
        }
    }
}

fn main() {
    foo::bar::baz();
}

/*  project_name::foo::bar  が表示される*/

include!

これもなかなかおもしろい。
include_str!はファイルの内容を文字列リテラルで展開しますが、
これはそのまま展開します。
C言語のincludeに似ていますね。

hello.rs
println!("hello!")
fn main(){
  include!("hello.rs");
}

コンパイルするとinclude!("hello.rs")がprintln!("hello!")に置き換わり、
実行するとhello!が出力されます。

try!

正直これは、チュートリアルにも乗っているから、書くかどうか迷ったんですが、とても便利なマクロですので、紹介します。
一応チュートリアルのURLも貼っておこう。
http://rust-lang-ja.github.io/the-rust-programming-language-ja/1.6/book/error-handling.html
try!はResultの中身の取り出しをunwrapより安全に、かつパターンマッチを書かず簡潔に行えます。

例を見せたほうが早いだろう。

fn foo() -> Result<i32, ()> {
    Result::Ok(777)
}

fn bar() -> Result<i32, ()> {
    let num = try!{foo()};
    println!("{}", num);
    Ok(num)
}

fn main() {
    bar();
}

/* 777が表示される */

このようにして、try!を使うと中身を簡単に取り出せます。
unwrapと違うところは、値がErrだとしてもpanic!にはならずにreturnされるところ。

正確ではないが、さっきのbar関数のtry!の部分を置き換えるとこうなる。

fn bar() -> Result<i32, ()> {
    let num = match foo() {
        Ok(x) => x,
        Err(x) => return Err(x),
    };
    println!("{}", num);
    Ok(num)
}

正確なtry!マクロの詳細についてはこちらのソースコードを参照するといい
https://doc.rust-lang.org/src/core/macros.rs.html#363-371

しかし、ここまで紹介しておいてなんだがtry!マクロを使うことは少ないかも知れない。
それは次に説明する。

?演算子(オマケ)

先程、try!マクロを紹介したが、これを簡略化した演算子が?演算子です。
この演算子はtry!の糖衣構文で式をカッコで囲む手間をなくすことができます。

try!で見せたbar関数を?演算子で書き換えた例はこう。

fn bar() -> Result<i32, ()> {
    let num = foo()?;
    println!("{}", num);
    Ok(num)
}

おわり