注意: 最新版のドキュメントをご覧ください。この第1版ドキュメントは古くなっており、最新情報が反映されていません。リンク先のドキュメントが現在の Rust の最新のドキュメントです。
Rustが提供している多くのコードの再利用や抽象化に利用できるツールを学びました。 それらのコードの再利用のユニットは豊富な意味論的構造を持っています。 例えば、関数は型シグネチャ、型パラメータはトレイト境界、オーバーロードされた関数はトレイトに所属していなければならない等です。
このような構造はRustのコアの抽象化が強力なコンパイル時の正確性のチェックを持っているという事を意味しています。 しかし、それは柔軟性の減少というコストを払っています。 もし、視覚的に繰り返しているコードのパターンを発見した時に、 それらをジェネリックな関数やトレイトや、他のRustのセマンティクスとして表現することが困難であると気がつくかもしれません。
マクロは構文レベルでの抽象化をすることを可能にします。 マクロ呼出は「展開された」構文への短縮表現です。 展開はコンパイルの初期段階、すべての静的なチェックが実行される前に行われます。 その結果として、マクロはRustのコアの抽象化では不可能な多くのパターンのコードの再利用を可能としています。
マクロベースのコードの欠点は、組み込みルールの少なさに由来するそのコードの理解のしづらさです。 普通の関数と同じように、良いマクロはその実装について理解しなくても使うことができます。 しかしながら、そのような良いマクロを設計するのは困難です! 加えて、マクロコード中のコンパイルエラーは開発者が書いたソースレベルではなく、 展開した結果のコードの中の問題について書かれているために、とても理解しづらいです。
これらの欠点はマクロを「最終手段となる機能」にしています。 これは、マクロが良くないものだと言っているわけではありません、マクロはRustの一部です、 なぜならばマクロを使うことで簡潔になったり、適切な抽象化が可能になる場面がしばしば存在するからです。 ただ、このトレードオフを頭に入れておいて欲しいのです。
vec!
マクロを見たことがあるでしょう、 ベクタ を任意の要素で初期化するために使われていました。
let x: Vec<u32> = vec![1, 2, 3];
vec!
は通常の関数として定義することはできません、なぜなら vec!
は任意の個数の引数を取るためです。
しかし、 vec!
を以下のコードの構文上の短縮形であると考えることができます:
let x: Vec<u32> = { let mut temp_vec = Vec::new(); temp_vec.push(1); temp_vec.push(2); temp_vec.push(3); temp_vec };
このような短縮形をマクロ: 1 を用いることで実装することができます
macro_rules! vec { ( $( $x:expr ),* ) => { { let mut temp_vec = Vec::new(); $( temp_vec.push($x); )* temp_vec } }; } fn main() { assert_eq!(vec![1,2,3], [1, 2, 3]); }macro_rules! vec { ( $( $x:expr ),* ) => { { let mut temp_vec = Vec::new(); $( temp_vec.push($x); )* temp_vec } }; }
ワオ!たくさんの新しい構文が現れました!細かく見ていきましょう。
fn main() { macro_rules! vec { ... } }macro_rules! vec { ... }
これは、新しいマクロ vec
を定義していることを意味しています、vec
という関数を定義するときに fn vec
と書くのと同じです。
非公式ですが、実際には、マクロ名をエクスクラメーションマーク(!) と共に記述します、例えば: vec!
のように示します。
エクスクラメーションマークはマクロ呼び出しの構文の一部で、マクロと通常の関数の区別をつけるためのものです。
マクロは、幾つかのパターンマッチのケースを利用したルールに従って定義されています、 上のコード中では、以下の様なパターンが見られました:
fn main() { ( $( $x:expr ),* ) => { ... }; }( $( $x:expr ),* ) => { ... };
これは match
式の腕に似ていますが、Rustの構文木に対してコンパイル時にマッチします。
セミコロンはケースの末尾でだけ使うことのでき、省略可能です。
=>
の左辺にある「パターン」は「マッチャ」として知られています。
マッチャは 小さなマッチャ独自の構文 を持っています。
マッチャ $x:expr
は任意のRustの式にマッチし、マッチした構文木を「メタ変数」 $x
に束縛します。
識別子 expr
は「フラグメント指定子」です。全てのフラグメント指定子の一覧はこの章で後ほど紹介します。
マッチャを $(...),*
で囲むと0個以上のコンマで句切られた式にマッチします。
特別なマッチャ構文は別にして、マッチャ中に登場するその他の任意のトークンはそれ自身に正確にマッチする必要があります。 例えば:
macro_rules! foo { (x => $e:expr) => (println!("mode X: {}", $e)); (y => $e:expr) => (println!("mode Y: {}", $e)); } fn main() { foo!(y => 3); }macro_rules! foo { (x => $e:expr) => (println!("mode X: {}", $e)); (y => $e:expr) => (println!("mode Y: {}", $e)); } fn main() { foo!(y => 3); }
上のコードは以下の様な出力をします
mode Y: 3
また、以下のようなコードでは
fn main() { foo!(z => 3); }foo!(z => 3);
以下の様なコンパイルエラーが発生します
error: no rules expected the token `z`
マクロルールの右辺は大部分が通常のRustの構文です。
しかし、マッチャによってキャプチャされた構文を繋げる事ができます。
最初に示した vec!
の例を見てみましょう:
$( temp_vec.push($x); )*
$x
にマッチしたそれぞれの式はマクロ展開中に push
文を生成します。
マクロ展開中の繰り返しはマッチャ中の繰り返しと足並みを揃えて実行されます(これについてはもう少し説明します)。
$x
が既に式にマッチすると宣言されているために、=>
の右辺では :expr
を繰り返しません。
また、区切りのコンマは繰り返し演算子の一部には含めません。
そのかわり、繰り返しブロックをセミコロンを用いて閉じます。
そのほかの詳細としては: vec!
マクロは 2つ の括弧のペアを右辺に含みます。
それらの括弧はよく以下のように合せられます:
macro_rules! foo { () => {{ ... }} }
外側の括弧は macro_rules!
構文の一部です。事実、()
や []
をかわりに使うことができます。
括弧は単純に右辺を区切るために利用されています。
内側の括弧は展開結果の一部です。
vec!
マクロは式を必要としているコンテキストで利用されていることを思いだしてください。
複数の文や、 let
束縛を含む式を書きたいときにはブロックを利用します。
もし、マクロが単一の式に展開されるときは、追加の括弧は必要ありません。
マクロが式を生成すると 宣言 した事はないという点に注意してください。 事実、それはマクロを式として利用するまでは決定されません。 注意深くすれば、複数のコンテキストで適切に展開されるマクロを書く事ができます。 例えば、データ型の短縮形は、式としてもパターンとしても正しく動作します。
繰り返し演算子は以下の2つの重要なルールに従います:
$(...)*
は繰り返しの一つの「レイヤ」上で動作し、 レイヤが含んでいる $name
について足並みを揃えて動作します。$name
はマッチしたときと同じ個数の $(...)*
の内側になければなりません。
もし更に多くの $(...)*
の中に表われた際には適切に複製されます。以下の複雑なマクロは一つ外の繰り返しのレベルから値を複製している例です:
macro_rules! o_O { ( $( $x:expr; [ $( $y:expr ),* ] );* ) => { &[ $($( $x + $y ),*),* ] } } fn main() { let a: &[i32] = o_O!(10; [1, 2, 3]; 20; [4, 5, 6]); assert_eq!(a, [11, 12, 13, 24, 25, 26]); }macro_rules! o_O { ( $( $x:expr; [ $( $y:expr ),* ] );* ) => { &[ $($( $x + $y ),*),* ] } } fn main() { let a: &[i32] = o_O!(10; [1, 2, 3]; 20; [4, 5, 6]); assert_eq!(a, [11, 12, 13, 24, 25, 26]); }
上のコードはほとんどのマッチャの構文を利用しています。
この例では0個以上にマッチする $(...)*
を利用しています、
1つ以上にマッチさせたい場合は $(...)+
を代わりに利用する事ができます。
また、どちらも補助的に区切りを指定する事ができます。区切りには、 +
と *
以外の任意のトークンを指定することが可能です。
このシステムは: "Macro-by-Example" (PDFリンク) に基づいています。
いくつかの言語に組込まれているマクロは単純なテキストの置換を用いています、しかしこれは多くの問題を発生させます。
例えば、以下のC言語のプログラムは期待している 25
の代わりに 13
と出力します:
#define FIVE_TIMES(x) 5 * x
int main() {
printf("%d\n", FIVE_TIMES(2 + 3));
return 0;
}
展開した結果は 5 * 2 + 3
となり、乗算は加算よりも優先度が高くなります。
もしC言語のマクロを頻繁に利用しているなら、この問題を避けるためのイディオムを5、6個は知っているでしょう。
Rustではこのような問題を恐れる必要はありません。
macro_rules! five_times { ($x:expr) => (5 * $x); } fn main() { assert_eq!(25, five_times!(2 + 3)); }
メタ変数 $x
は一つの式の頂点としてパースされ、構文木上の位置は置換されたあとも保存されます。
他のマクロシステムで良くみられる問題は、「変数のキャプチャ」です。 以下のC言語のマクロは GNU C拡張 をRustの式のブロックをエミュレートするために利用しています。
#define LOG(msg) ({ \
int state = get_log_state(); \
if (state > 0) { \
printf("log(%d): %s\n", state, msg); \
} \
})
以下はこのマクロを利用したときにひどい事になる単純な利用例です:
const char *state = "reticulating splines";
LOG(state)
このコードは以下のように展開されます
const char *state = "reticulating splines";
{
int state = get_log_state();
if (state > 0) {
printf("log(%d): %s\n", state, state);
}
}
2番目の変数 state
は1つめの state
を隠してしまいます。
この問題は、print文が両方の変数を参照する必要があるために起こります。
Rustにおける同様のマクロは期待する通りの動作をします。
fn get_log_state() -> i32 { 3 } macro_rules! log { ($msg:expr) => {{ let state: i32 = get_log_state(); if state > 0 { println!("log({}): {}", state, $msg); } }}; } fn main() { let state: &str = "reticulating splines"; log!(state); }macro_rules! log { ($msg:expr) => {{ let state: i32 = get_log_state(); if state > 0 { println!("log({}): {}", state, $msg); } }}; } fn main() { let state: &str = "reticulating splines"; log!(state); }
このマクロはRustが 健全なマクロシステム を持っているためです。
それぞれのマクロ展開は分離された「構文コンテキスト」で行なわれ、
それぞれの変数はその変数が導入された構文コンテキストでタグ付けされます。
これは、 main
中の state
がマクロの中の state
とは異なる「色」で塗られているためにコンフリクトしないという風に考える事ができます。
この健全性のシステムはマクロが新しい束縛を呼出時に導入する事を制限します。 以下のようなコードは動作しません:
macro_rules! foo { () => (let x = 3); } fn main() { foo!(); println!("{}", x); }macro_rules! foo { () => (let x = 3); } fn main() { foo!(); println!("{}", x); }
呼出時に渡す事で正しい構文コンテキストでタグ付けされるように、代わりに変数名を呼出時に渡す必要があります。
macro_rules! foo { ($v:ident) => (let $v = 3); } fn main() { foo!(x); println!("{}", x); }macro_rules! foo { ($v:ident) => (let $v = 3); } fn main() { foo!(x); println!("{}", x); }
このルールは let
束縛やループについても同様ですが、 アイテム については適用されません。
そのため、以下のコードはコンパイルが通ります:
macro_rules! foo { () => (fn x() { }); } fn main() { foo!(); x(); }
マクロの展開は、展開中のマクロ自身も含めたその他のマクロ呼出しを含んでいることが可能です。 そのような再帰的なマクロは、以下の(単純化した)HTMLの短縮形のような、木構造を持つ入力の処理に便利です:
#![allow(unused_must_use)] macro_rules! write_html { ($w:expr, ) => (()); ($w:expr, $e:tt) => (write!($w, "{}", $e)); ($w:expr, $tag:ident [ $($inner:tt)* ] $($rest:tt)*) => {{ write!($w, "<{}>", stringify!($tag)); write_html!($w, $($inner)*); write!($w, "</{}>", stringify!($tag)); write_html!($w, $($rest)*); }}; } fn main() { // FIXME(#21826) use std::fmt::Write; let mut out = String::new(); write_html!(&mut out, html[ head[title["Macros guide"]] body[h1["Macros are the best!"]] ]); assert_eq!(out, "<html><head><title>Macros guide</title></head>\ <body><h1>Macros are the best!</h1></body></html>"); }macro_rules! write_html { ($w:expr, ) => (()); ($w:expr, $e:tt) => (write!($w, "{}", $e)); ($w:expr, $tag:ident [ $($inner:tt)* ] $($rest:tt)*) => {{ write!($w, "<{}>", stringify!($tag)); write_html!($w, $($inner)*); write!($w, "</{}>", stringify!($tag)); write_html!($w, $($rest)*); }}; } fn main() { use std::fmt::Write; let mut out = String::new(); write_html!(&mut out, html[ head[title["Macros guide"]] body[h1["Macros are the best!"]] ]); assert_eq!(out, "<html><head><title>Macros guide</title></head>\ <body><h1>Macros are the best!</h1></body></html>"); }
マクロの展開結果を見るには、 rustc --pretty expanded
を実行して下さい。
出力結果はクレートの全体を表しています、そのため出力結果を再び rustc
に与えることができます、
そのようにすると時々、直接コンパイルした場合よりもより良いエラーメッセージを得ることができます。
しかし、 --pretty expanded
は同じ名前の変数(構文コンテキストは異なる)が同じスコープに複数存在する場合、
出力結果のコード自体は、元のコードと意味が変わってくる場合があります。
そのようになってしまう場合、 --pretty expanded,hygiene
のようにすることで、構文コンテキストについて知ることができます。
rustc
はマクロのデバッグを補助する2つの構文拡張を提供しています。
今のところは、それらの構文は不安定であり、フィーチャーゲートを必要としています。
log_syntax!(...)
は与えられた引数をコンパイル時に標準入力に出力し、展開結果は何も生じません。trace_macros!(true)
はマクロが展開されるたびにコンパイラがメッセージを出力するように設定できます、
trace_macros!(false)
を展開の終わりごろに用いることで、メッセージの出力をオフにできます。Rustのコードに展開されていないマクロが含まれていても、 構文木 としてパースすることができます。 このような特性はテキストエディタや、その他のコードを処理するツールにとって非常に便利です。 また、このような特性はRustのマクロシステムの設計にも影響を及ぼしています。
一つの影響としては、マクロ呼出をパースした時、マクロが以下のどれを意味しているかを判定する必要があります:
ブロック中でのマクロ呼出は、幾つかのアイテムや、一つの式 / 文 に対応します。 Rustはこの曖昧性を判定するために単純なルールを利用します。 アイテムに対応しているマクロ呼出は以下のどちらかでなければなりません
foo! { ... }
foo!(...);
その他の展開前にパース可能である事による制約はマクロ呼出は正しいRustトークンで構成されている必要があるというものです。
そのうえ、括弧や、角カッコ、波括弧はマクロ呼出し中でバランスしてなければなりません。
例えば: foo!([)
は禁止されています。
これによってRustはマクロ呼出しがどこで終わっているかを知ることができます。
もっと厳密に言うと、マクロ呼出しの本体は「トークンの木」のシーケンスである必要があります。 トークンの木は以下のいずれかの条件により再帰的に定義されています
()
、 []
または {}
で囲まれたトークンの木、あるいは、マッチャ内部ではそれぞれのメタ変数はマッチする構文を指定する「フラグメント指定子」を持っています。
ident
: 識別子。 例: x
; foo
path
: 修飾された名前。例: T::SpecialA
expr
: 式。 例: 2 + 2
; if true { 1 } else { 2 }
; f(42)
ty
: 型。 例: i32
; Vec<(char, String)>
; &T
pat
: パターン。 例: Some(t)
; (17, 'a')
; _
stmt
: 単一の文。 例: let x = 3
block
: 波括弧で区切られた文のシーケンスと場合によっては式も付く。 例: { log(error, "hi"); return 12 }
item
: アイテム。 例: fn foo() { }
; struct Bar;
meta
: アトリビュートで見られるような「メタアイテム」。 例: cfg(target_os = "windows")
tt
: 単一のトークンの木またメタ変数の次のトークンについて以下のルールが存在します:
expr
変数と stmt
変数は => , ;
のどれか一つのみが次に現れますty
と path
変数は => , = | ; : > [ { as where
のどれか一つのみが次に現れますpat
変数は => , = | if in
のどれか一つのみが次に現れますこれらのルールは既存のマクロを破壊すること無くRustの構文を拡張するための自由度を与えます。
マクロシステムはパースの曖昧さについては何も対処しません。
例えば、 $($i:ident)* $e:expr
は常にパースが失敗します、
なぜならパーサーは $i
をパースするか、 $e
をパースするかを選ぶことを強制されるためです。
呼出構文を変更して識別可能なトークンを先頭につけることでこの問題は回避することができます。
そのようにする場合、例えば $(I $i:ident)* E $e:expr
のように書くことができます。
マクロはコンパイルの早い段階、名前解決が行われる前に展開されます。 一つの悪い側面としては、言語中のその他の構造とは異なり、マクロではスコープが少し違って動作するということです。
マクロの定義と展開はクレートの字面上の順序どおりに単一の深さ優先探索で行われます。
そのため、モジュールスコープで定義されたマクロは、
後続する子供の mod
アイテムも含む、同じモジュール中のコードから見えます。
fn
の本体の中やその他のモジュールのスコープでない箇所で定義されたマクロはそのアイテム中でしか見えません。
もし、モジュールが macro_use
アトリビュートを持っていた場合、
それらのマクロは子供の mod
アイテムの後で、親モジュールからも見えます。
もし親モジュールが同様に macro_use
アトリビュートを持っていた場合、 親の親モジュールから親の mod
アイテムが終わった後に見えます。
その後についても同様です。
また、 macro_use
アトリビュートは extern crate
の上でも利用することができます。
そのようにした場合、 macro_use
アトリビュートは外部のクレートからどのマクロをロードするのかを指定します。
以下がその例です:
#[macro_use(foo, bar)] extern crate baz;
もしアトリビュートが単純に #[macro_use]
という形で指定されていた場合、全てのマクロがロードされます。
もし、 #[macro_use]
が指定されていなかった場合、 #[macro_export]
アトリビュートとともに定義されているマクロ以外は、
どのマクロもロードされません。
クレートのマクロを出力にリンクさせずにロードするには、 #[no_link]
を利用して下さい。
一例としては:
macro_rules! m1 { () => (()) } // // visible here: m1 // ここで見えるのは: m1 mod foo { // // visible here: m1 // ここで見えるのは: m1 #[macro_export] macro_rules! m2 { () => (()) } // // visible here: m1, m2 // ここで見えるのは: m1、m2 } // // visible here: m1 // ここで見えるのは: m1 macro_rules! m3 { () => (()) } // // visible here: m1, m3 // ここで見えるのは: m1、m3 #[macro_use] mod bar { // // visible here: m1, m3 // ここで見えるのは: m1、m3 macro_rules! m4 { () => (()) } // // visible here: m1, m3, m4 // ここで見えるのは: m1、m3、m4 } // // visible here: m1, m3, m4 // ここで見えるのは: m1、m3、m4 fn main() { }macro_rules! m1 { () => (()) } // ここで見えるのは: m1 mod foo { // ここで見えるのは: m1 #[macro_export] macro_rules! m2 { () => (()) } // ここで見えるのは: m1、m2 } // ここで見えるのは: m1 macro_rules! m3 { () => (()) } // ここで見えるのは: m1、m3 #[macro_use] mod bar { // ここで見えるのは: m1、m3 macro_rules! m4 { () => (()) } // ここで見えるのは: m1、m3、m4 } // ここで見えるのは: m1、m3、m4
ライブラリが #[macro_use]
と共に外部のクレートをロードした場合、 m2
だけがインポートされます。
Rustのリファレンスは マクロに関連するアトリビュートの一覧 を掲載しています。
$crate
変数さらなる困難はマクロが複数のクレートで利用された時に発生します。
mylib
が以下のように定義されているとしましょう
pub fn increment(x: u32) -> u32 { x + 1 } #[macro_export] macro_rules! inc_a { ($x:expr) => ( ::increment($x) ) } #[macro_export] macro_rules! inc_b { ($x:expr) => ( ::mylib::increment($x) ) }
inc_a
は mylib
の中でだけ動作します、かたや inc_b
は mylib
の外部でだけ動作します。
さらにいえば、 inc_b
はユーザーが mylib
を異なる名前でインポートした際には動作しません。
Rustは(まだ)健全なクレートの参照の仕組みを持っていません、
しかし、この問題に対する簡単な対処方法を提供しています。
foo
というクレートからインポートされたマクロ中において、
特別なマクロ変数 $crate
は ::foo
に展開されます。
対照的に、マクロが同じクレートの中で定義され利用された場合、
$crate
は何にも展開されません。これはつまり以下のように書けることを意味しています:
#[macro_export] macro_rules! inc { ($x:expr) => ( $crate::increment($x) ) }
これは、ライブラリの中でも外でも動作するマクロを定義しています。
関数の名前は ::increment
または ::mylib::increment
に展開されます。
このシステムを簡潔で正しく保つために、 #[macro_use] extern crate ...
はクレートのルートにしか登場せず、
mod
の中には現れません。
入門のチャプタで再帰的なマクロについて言及しました、しかしそのチャプタでは詳細について話していませんでした。 再帰的なマクロが便利な他の理由は、それぞれの再帰的な呼出はマクロに与えられた引数にたいしてパターンマッチを行える可能性を与えてくれることです。
極端な例としては、 望ましくはありませんが、 Bitwise Cyclic Tag のオートマトンをRustのマクロで実装する事が可能です。
fn main() { macro_rules! bct { // cmd 0: d ... => ... (0, $($ps:tt),* ; $_d:tt) => (bct!($($ps),*, 0 ; )); (0, $($ps:tt),* ; $_d:tt, $($ds:tt),*) => (bct!($($ps),*, 0 ; $($ds),*)); // cmd 1p: 1 ... => 1 ... p (1, $p:tt, $($ps:tt),* ; 1) => (bct!($($ps),*, 1, $p ; 1, $p)); (1, $p:tt, $($ps:tt),* ; 1, $($ds:tt),*) => (bct!($($ps),*, 1, $p ; 1, $($ds),*, $p)); // cmd 1p: 0 ... => 0 ... (1, $p:tt, $($ps:tt),* ; $($ds:tt),*) => (bct!($($ps),*, 1, $p ; $($ds),*)); // // halt on empty data string // 空のデータ文字列で停止します ( $($ps:tt),* ; ) => (()); } }macro_rules! bct { // cmd 0: d ... => ... (0, $($ps:tt),* ; $_d:tt) => (bct!($($ps),*, 0 ; )); (0, $($ps:tt),* ; $_d:tt, $($ds:tt),*) => (bct!($($ps),*, 0 ; $($ds),*)); // cmd 1p: 1 ... => 1 ... p (1, $p:tt, $($ps:tt),* ; 1) => (bct!($($ps),*, 1, $p ; 1, $p)); (1, $p:tt, $($ps:tt),* ; 1, $($ds:tt),*) => (bct!($($ps),*, 1, $p ; 1, $($ds),*, $p)); // cmd 1p: 0 ... => 0 ... (1, $p:tt, $($ps:tt),* ; $($ds:tt),*) => (bct!($($ps),*, 1, $p ; $($ds),*)); // 空のデータ文字列で停止します ( $($ps:tt),* ; ) => (()); }
演習: マクロを使って上の bct!
マクロの定義の重複している部分を減らしてみましょう。
以下は、Rustコード中でよく見られるマクロたちです。
このマクロは現在のスレッドをパニック状態にします。 パニック時のメッセージを指定することができます。
fn main() { panic!("oh no!"); }panic!("oh no!");
vec!
マクロはこの本のなかで使われてきましたので、 すでに見たことがあるでしょう。
vec!
マクロは Vec<T>
を簡単に作成できます:
let v = vec![1, 2, 3, 4, 5];
また、値の繰り返しのベクタを作成することも可能です。 たとえば、以下は100個の0を含むベクタの例です:
fn main() { let v = vec![0; 100]; }let v = vec![0; 100];
この2つのマクロはテスト時に利用されています。
assert!
は真偽値を引数に取ります。
assert_eq!
は2つの等価性をチェックする値を引数に取ります。
true
ならばパスし、 false
だった場合 panic!
を起こします:
// Okです! assert!(true); assert_eq!(5, 3 + 2); // 駄目だぁ :( assert!(5 < 3); assert_eq!(5, 3);
try!
はエラーハンドリングのために利用されています。
try!
は Result<T, E>
を返す何らかの物を引数に取り、もし Result<T, E>
が Ok<T>
だった場合 T
を返し、
そうでなく Err(E)
だった場合はそれを return
します。
例えば以下のように利用します:
use std::fs::File; fn foo() -> std::io::Result<()> { let f = try!(File::create("foo.txt")); Ok(()) }
このコードは以下のコードよりも綺麗です:
fn main() { use std::fs::File; fn foo() -> std::io::Result<()> { let f = File::create("foo.txt"); let f = match f { Ok(t) => t, Err(e) => return Err(e), }; Ok(()) } }use std::fs::File; fn foo() -> std::io::Result<()> { let f = File::create("foo.txt"); let f = match f { Ok(t) => t, Err(e) => return Err(e), }; Ok(()) }
このマクロはあるコードが絶対に実行されるべきでないと考えている時に利用します。
fn main() { if false { unreachable!(); } }if false { unreachable!(); }
時々、コンパイラによって絶対に呼び出されるはずがないと考えているブランチを作成することになる時があります。
そういった時には、このマクロを利用しましょう、そうすることでもし何か誤ってしまった時に、 panic!
で知ることができます。
let x: Option<i32> = None; match x { Some(_) => unreachable!(), None => println!("I know x is None!"), }
unimplemented!
マクロはもし関数の本体の実装はしていないが、型チェックだけは行いたいという時に利用します。
このような状況の一つの例としては複数のメソッドを必要としているトレイトのメソッドの一つを実装しようと試みている時などです。
残りのメソッドたちの実装に取り掛かれるようになるまで unimplemented!
として定義しましょう。
もしRustのマクロシステムでは必要としていることができない場合、
コンパイラプラグイン を代わりに書きたくなるでしょう。
コンパイラプラグインは macro_rules!
マクロとくらべて、更に多くの作業が必要になり、
インタフェースはかなり不安定であり、バグはさらに追跡が困難になります。
引き換えに、任意のコードをコンパイラ中で実行できるという自由度を得ることができます。
構文拡張プラグインがしばしば「手続きマクロ」と呼ばれるのはこのためです。
vec!
のlibcollectionsにおける実際の実装と、ここで示したコードは効率性や再利用性のために異なります。 ↩