Rust by Example
Rustは安全性、速度、並行性に焦点を当てたモダンなシステム プログラミング言語です。これができるのは、ガベージコレクションなしでメモリ 安全であるためです。
Rust by Example (RBE)は、Rustの様々な概念や標準ライブラリを実行できる例で紹介 したサンプルコード集です。この例を活用するために、Rustをローカルに インストールして、公式ドキュメントを参照することを忘れないで ください。 興味がある方はこのサイトのソースコードもあります。(日本語版はこちら 。)
それでは、始めましょう!
-
Hello World - 伝統的な「Hello World」プログラムから始めます。
-
プリミティブ - 符号付き整数、符号なし整数などのプリミティブについて学びます。
-
カスタム型 -
structとenum -
変数束縛 - 可変束縛、スコープ、シャドーイング。
-
型 - 型の変更と宣言について学びます。
-
制御フロー -
if/else、forなど。 -
関数 - メソッド、クロージャ、高階関数などについて学びます。
-
モジュール - モジュールでコードを整理する。
-
クレート - クレートはRustの編集ユニットです。ライブラリの作り方を学びます。
-
Cargo - Rustの標準パッケージマネージャの基本的な機能について学びます。
-
属性 - 属性とは、モジュールやクレート、その要素などに対して適用されるメタデータのことです。
-
ジェネリック - 複数の型の引数に対して実行できる関数やデータ型について学びます.
-
スコープのルール - スコープは所有権、借用、ライフタイムに関して重要な役割を担います。
-
トレイト - トレイとは未知の型
Selfに対して実装されたメソッドの集合です。 -
エラー処理 - Rustで失敗を処理する方法について学びます。
-
Stdライブラリの型 -
stdライブラリで提供されるいくつかのカスタム型について学びます。 -
その他のStd - ファイル処理、スレッドなどのその他のカスタム型。
-
テスト - Rustにおけるすべての種類のテスト。
-
メタデータ - ドキュメント、ベンチマーク。
Hello World
こちらが伝統的な「Hello World」プログラムのソースコードです。
// これはコメントで、コンパイラに無視されます。 // ここにある"Run"ボタンをクリックするとコードをテストできます -> // キーボードを使いたければ、"Ctrl + Enter"ショートカットを使うことができます // このコードは編集できます。自由にハックしてください! // "Reset"ボタンをクリックするといつでも元のコードに戻すことができます -> // これがmain関数です fn main() { // ここにある式はバイナリが呼び出されたときに実行されます。 // テキストをコンソールに出力します println!("Hello World!"); }
println!はテキストをコンソールに出力するマクロです。
Rustのコンパイラrustcを使うことでバイナリを生成できます。
$ rustc hello.rs
rustcはhello実行可能バイナリを出力します。
$ ./hello
Hello World!
演習
'Run'で出力を確認してください. 次に, このように出力する
、2つ目のprintln!マクロを使った行を追加してください。
Hello World!
I'm a Rustacean!
コメント
どんなプログラムもコメントを必要とします。Rustはいくつかの形 をサポートしています。
- コンパイラに無視される普通のコメント
// 行の終わりまで続く行コメント。/* コメントを終了するデリミタまで続くブロックコメント */
- HTMLライブラリに解析されるDocコメント
ドキュメント:
/// 下の要素のライブラリドキュメントを生成する。//! //!で囲った要素のライブラリドキュメントを生成する。
fn main() { // これは行コメントの例です。 // 行頭に2つのスラッシュがあります。 // この中のものはすべてコンパイラに読まれません。 // println!("Hello, world!"); // これを実行してみてください。わかりましたか? 次に2つのスラッシュを消して、もう1度実行してください。 /* * これはもう1つのタイプのコメント、ブロックコメントです。通常、 * 行コメントが推奨されます。しかし、ブロックコメントはある部分のコードを * 無効化するのに有用です。/* ブロックコメントは /* ネストできます。 */ */ * なので少しのキーストロークで main()関数をすべてコメントアウトできます。 * /*/*/* 試してみてください! */*/*/ */ /* 注意: 前の行の`*`は完全にスタイルのためのものです。本当は 必要ありません。 */ // ブロックコメントで、行コメントよりも簡単に式を // 操ることができます。 コメントデリミタを削除して // 結果を変えてみてください。 let x = 5 + /* 90 + */ 5; println!("Is `x` 10 or 100? x = {}", x); }
こちらも参照:
フォーマットしてプリント
プリントはstd::fmtで定義されているいくつかのマクロによって
制御されています。そのいくつかを紹介します。
format!: フォーマットされたテキストをStringに書き込む。print!:format!と同様だが、コンソール(io::stdout)に書き込む。println!:print!と同様だが、改行が追加される。eprint!:format!と同様だが、標準エラー出力(io::stderr)に書き込む。eprintln!:eprint!と同様だが、改行が追加される。
すべてのものは、同じやり方で解析し、 コンパイル時に正しくフォーマットできるか チェックします。
fn main() { // 一般的に、`{}`が自動的に引数に変換されます。 // 自動的に文字列に変換します。 println!("{} days", 31); // {}日 // 示唆しなければ、31はi32になります。示唆を加えることで、31の型を // 変えられます。例えば31i64はi64として解釈されます。 // 引数の位置を使って、埋め込まれる場所を指定することができます。 println!("{0}, this is {1}. {1}, this is {0}", "Alice", "Bob"); // {0}、こちらが{1}です。{1}、こちらが{0}です。 // 名前を付けても良いです。 println!("{subject} {verb} {object}", // 素早い茶色の狐はのろまな犬を飛び越える object="the lazy dog", subject="the quick brown fox", verb="jumps over"); // `:`の後にフォーマット型を指定すると特殊なフォーマットができます。 println!("{} of {:b} people know binary, the other half doesn't", 1, 2); // {:b}(2つ目)人に{}(1つ目)人はバイナリを知っていますが、残りの半分は知りません。 // 幅を指定して右寄せにできます。 この例は // " 1". 5 white spaces and a "1"と出力します。 println!("{number:>width$}", number=1, width=6); // 余分なゼロで数字を埋めることができます。この例は"000001"と出力します。 println!("{number:>0width$}", number=1, width=6); // Rustは指定された数の引数が使われているかもチェック // します。 println!("My name is {0}, {1} {0}", "Bond"); // 私の名前は{0}、{1} {0}です。 // FIXME ^ 足りない引数、"James"を追加してください。 // `i32`を含む`Structure`という構造体を作ります。 #[allow(dead_code)] struct Structure(i32); // しかし、この構造体のようなカスタム型はもう少し複雑です。 // これは動作しません。 println!("This struct `{}` won't print...", Structure(3)); // この構造体`{}`は出力できません... // FIXME ^ この行をコメントアウトしてください。 }
std::fmtはテキストを出力するための多くのトレイトを持って
います。2つの重要なものを挙げます。
fmt::Debug:{:?}マーカーを使います。デバッグ用にテキストをフォーマットします。fmt::Display:{}マーカーを使います。もっと美しく、ユーザーフレンドリーに 表示します。
この例で使われている型は、標準ライブラリに含まれているため、ここではfmt::Display
を使用しています。カスタム型を扱う場合はもう少し複雑です。
fmt::Displayトレイトを実装すると、自動的にStringに変換する
ToStringトレイトが実装されます。
演習
- 上のコードの、2つの箇所を変更して(FIXMEを見てください)エラーなく実行できる ようにしてください。
- 表示される小数の桁数を調整し、
Pi is roughly 3.142(Piは大体3.142です) と出力されるprintln!マクロを追加してください。ただし、円周率の値はlet pi = 3.141592を使ってください。(ヒント: 小数の桁数を調整して出力 する方法について、std::fmtドキュメントをチェックしてみてください。)
こちらも参照:
デバッグ
std::fmtのフォーマット用のトレイトを使用したい型は、すべてプリント可能
なように実装されている必要があります。stdライブラリにあるような型にしか
実装は提供されていません。他は手動で実装しなければいけません。
fmt::Debugというトレイトでこれを簡略化できます。すべての型は
fmt::Debugの実装を継承(自動で作成)できるからです。ただし、
fmt::Displayの場合は手動で実装しなければいけません。
#![allow(unused_variables)] fn main() { // この構造体は`fmt::Display`でも // `fmt::Debug`でもプリントできません。 struct UnPrintable(i32); // `derive`属性は、自動的にこの`struct`を`fmt::Debug`で // プリントできるように実装を作成します。 #[derive(Debug)] struct DebugPrintable(i32); }
すべてのstdライブラリの型も自動的に{:?}でプリントできます。
// `fmt::Debug`の実装を`Structure`に継承する. `Structure` // は一つの`i32`をメンバに持っています。 #[derive(Debug)] struct Structure(i32); // `Structure`を中に持つ構造体`Deep`を作る。 これもプリント可能 // にする。 #[derive(Debug)] struct Deep(Structure); fn main() { // `{:?}`は`{}`と似たようなものです。 println!("{:?} months in a year.", 12); println!("{1:?} {0:?} is the {actor:?} name.", "Slater", "Christian", actor="actor's"); // `Structure`はプリントできます! println!("Now {:?} will print!", Structure(3)); // 今、{:?}がプリントされます。 // `derive`の問題点は、結果の見た目を制御できない点です。 // 出力を`7`だけにするにはどうすればよいでしょう? println!("Now {:?} will print!", Deep(Structure(7))); }
fmt::Debugは確実にプリントを可能にしてくれるのですが、ある意味で美しさを
犠牲にしています。Rustは{:#?}による「美しいプリント」も提供しています。
#[derive(Debug)] struct Person<'a> { name: &'a str, age: u8 } fn main() { let name = "Peter"; let age = 27; let peter = Person { name, age }; // 美しいプリント println!("{:#?}", peter); }
出力をコントロールするために手動でfmt::Displayを実装することもできます。
こちらも参照:
ディスプレイ
fmt::Debugはコンパクトでもきれいでもありません。なので、たいていの場合は
出力をカスタマイズするのが好ましいでしょう。これは{}プリントマーカーを使う
fmt::Displayを手動で実装することで可能です。次のように実装できます。
#![allow(unused_variables)] fn main() { // (`use`で) `fmt`モジュールをインポートすることで利用可能にします。 use std::fmt; // を`fmt::Display`実装する構造体を定義します。これは`i32`を含む // `Structure`というタプル構造体です。 struct Structure(i32); // `{}`マーカーを使用するには、その型のための`fmt::Display`トレイトが手動で // 実装されている必要があります。 impl fmt::Display for Structure { // このトレイとは`fmt`が想定通りのものであることを要求します。 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // 必ず最初の要素が出力されるようにします。 // `f`ストリームはオペレーションが成功したがどうかを表す`fmt::Result` // を返します。`write!`は`println!`にとても良く似た構文を持っていることに // 注目してください。 write!(f, "{}", self.0) } } }
fmt::Displayはfmt::Debugよりも簡潔に見えますが、stdライブラリでは
問題が生じます。曖昧な型ではどのように表示すればよいでしょうか?
例えば、std ライブラリがすべてのVec<T>に対して同じスタイルを提供する
とすれば、どのスタイルにすれば良いでしょう? 以下のどちらを選べばよいでしょうか?
Vec<path>:/:/etc:/home/username:/bin(:で分割)Vec<number>:1,2,3(,で分割)
どちらも正解ではありません。あらゆる方に対して理想的なスタイルは存在しませんし、
stdライブラリがそれを提供しているわけではありません。fmt::DisplayはVec<T>
などのジェネリックなコンテナには定義していません。このような場合はfmt::Debug
fmt::Debugを使用するべきです。
ジェネリックでないようなコンテナ型ではこのような問題は生じませんので、
fmt::Displayを実装できます。
use std::fmt; // `fmt`をインポート // 構造体は2つの値を持っています。出力を`Display`と比較するために // `Debug`をderiveしています。 #[derive(Debug)] struct MinMax(i64, i64); // `Display`を`MinMax`に実装する。 impl fmt::Display for MinMax { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // `self.数字`でそれぞれの位置のデータを参照できます。 write!(f, "({}, {})", self.0, self.1) } } // 比較のため、フィールド上の点に名前をつける構造体を定義しましょう。 #[derive(Debug)] struct Point2D { x: f64, y: f64, } // `Display`を`Point2D`にも実装しましょう impl fmt::Display for Point2D { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // `x`と`y`のみが明示的になるようにカスタマイズ。 write!(f, "x: {}, y: {}", self.x, self.y) } } fn main() { let minmax = MinMax(0, 14); println!("Compare structures:"); println!("Display: {}", minmax); println!("Debug: {:?}", minmax); let big_range = MinMax(-300, 300); let small_range = MinMax(-3, 3); println!("The big range is {big} and the small is {small}", small = small_range, big = big_range); let point = Point2D { x: 3.3, y: 7.2 }; println!("Compare points:"); println!("Display: {}", point); println!("Debug: {:?}", point); // `Debug`と`Display`は実装されていますが、`{:b}`は、`fmt::Binary` // が実装されていないと使えないので、この例はエラーになります。 // println!("What does Point2D look like in binary: {:b}?", point); }
fmt::Displayは実装されていますが、fmt::Binary されていないので、
使うことができません。std::fmtは多くのこのようなトレイトがあり、
それぞれに独自の実装が必要です。詳細はstd::fmtを参照してください。
演習
上の例の出力を確認して、Point2D構造体を参考にして、例として複素数構造体を定義
しましょう. うまくいけば、次のように出力されます。
Display: 3.3 + 7.2i
Debug: Complex { real: 3.3, imag: 7.2 }
こちらも参照:
テストケース: リスト
要素が順番に処理されていくような構造体に対してfmt::Displayを実装するのは
トリッキーです。なぜなら、それぞれのwrite!がfmt::Resultを返すからです。
適切に処理するにはすべてのResultに対して処理を行わなければいけません。
Rust はこのような目的のために?演算子を用意しています。
次のように?をwrite!に対して使えます。
// `write!`を実行し、エラーが出た場合はerrorを返し、
// そうでなければ処理を続行する。
write!(f, "{}", value)?;
もう1つ、同じように動くtry!マクロを使うこともできます。
これは少し冗長であり、もはや推奨されていませんが、古い
Rustのコードを読むときは。このようにtry!を使っています。
try!(write!(f, "{}", value));
?を使うことができれば、fmt::DisplayをVecに実装する
のはより簡単です。
use std::fmt; // `fmt`モジュールをインポートする。 // `vec`を含む構造体`List`を実装する。 struct List(Vec<i32>); impl fmt::Display for List { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // タプルインデックスで要素を展開し、 // `vec`という参照を作る。 let vec = &self.0; write!(f, "[")?; // `v`で`vec`を反復し、enumerateで // `count`にカウントを取得する。 for (count, v) in vec.iter().enumerate() { // 最初の要素以外、全ての要素の前にコンマをつける。 // ?演算子かtry!を使って、エラーのときにそれを返す。 if count != 0 { write!(f, ", ")?; } write!(f, "{}", v)?; } // 開いた括弧を閉じ、fmt::Resultを返す。 write!(f, "]") } } fn main() { let v = List(vec![1, 2, 3]); println!("{}", v); }
演習
ベクタのインデックスも表示するようにプログラムを変更しましょう。 新しい出力は以下のようになるはずです。
[0: 1, 1: 2, 2: 3]
こちらも参照:
フォーマット
文字列がどのようにフォーマットされるかはフォーマット文字列によって決まります。
format!("{}", foo)->"3735928559"format!("0x{:X}", foo)->"0xDEADBEEF"format!("0o{:o}", foo)->"0o33653337357"
同じ変数(foo)がX、o、指定なしのような様々な引数タイプ
によってフォーマットされます。
これらのフォーマットはそれぞれの引数タイプに応じたトレイトによって定義されています。
最も一般的なトレイとはDisplayで、これは引数タイプが未指定(例えば{})のときに
呼び出されます。
use std::fmt::{self, Formatter, Display}; struct City { name: &'static str, // 緯度 lat: f32, // 経度 lon: f32, } impl Display for City { // `f`はバッファで、ここにフォーマットした結果を書き込む必要があります。 fn fmt(&self, f: &mut Formatter) -> fmt::Result { let lat_c = if self.lat >= 0.0 { 'N' } else { 'S' }; let lon_c = if self.lon >= 0.0 { 'E' } else { 'W' }; // `write!`は`format!`のようなものですが、これはフォーマットされた文字列を // 第1引数に指定されたバッファに書き込みます。 write!(f, "{}: {:.3}°{} {:.3}°{}", self.name, self.lat.abs(), lat_c, self.lon.abs(), lon_c) } } #[derive(Debug)] struct Color { red: u8, green: u8, blue: u8, } fn main() { for city in [ City { name: "Dublin", lat: 53.347778, lon: -6.259722 }, City { name: "Oslo", lat: 59.95, lon: 10.75 }, City { name: "Vancouver", lat: 49.25, lon: -123.1 }, ].iter() { println!("{}", *city); } for color in [ Color { red: 128, green: 255, blue: 90 }, Color { red: 0, green: 3, blue: 254 }, Color { red: 0, green: 0, blue: 0 }, ].iter() { // fmt::Displayに実装を追加したら、そちらを使うように変更 // してください。 println!("{:?}", *color); } }
フォーマット用のトレイト一覧とその引数タイプstd::fmt
をドキュメントで確認できます。
演習
次のように出力する、上のColor構造体のためのfmt::Displayトレイトの実装を
してください。
RGB (128, 255, 90) 0x80FF5A
RGB (0, 3, 254) 0x0003FE
RGB (0, 0, 0) 0x000000
詰まったら次の2つがヒントになるかもしれません。
- それぞれの色を2回以上出力する必要があるかもしれません,
:02で2桁ゼロ埋め できます。
こちらも参照:
プリミティブ
Rustは、様々なプリミティブを提供しています。以下がその例です。
スカラー型
- 符号付き整数:
i8、i16、i32、i64、i128、そしてisize(ポインタサイズ) - 符号なし整数:
u8、u16、u32、u64、u128、そしてusize(ポインタサイズ) - 浮動小数点:
f32,f64 charUnicodeのスカラー値。例えば'a'、'α'、そして'∞'(それぞれ4バイト)booltrueまたはfalse- ユニット型。唯一の値が空タプル
()。
ユニット型はタプルですが、複数の値を含むことができないため、 複合型ではありません。
複合型
[1, 2, 3]のような配列(1, true)のようなタプル
変数は常に型指定可能です。数値型は更にサフィックスの指定が可能で、
指定しない場合はデフォルトになります。整数値のデフォルトはi32で、
浮動小数点数はf64です。Rustはさらに文脈から型推論ができることに注意
してください。
fn main() { // 変数には型の注釈が付けられます。 let logical: bool = true; let a_float: f64 = 1.0; // ふつうの注釈 let an_integer = 5i32; // サフィックスで注釈 // デフォルトを選択することもできます。 let default_float = 3.0; // `f64` let default_integer = 7; // `i32` // 文脈から型推論する let mut inferred_type = 12; // 他の行からi64であると推論する。 inferred_type = 4294967296i64; // 可変な変数は変更できます。 let mut mutable = 12; // 可変な`i32` mutable = 21; // エラーします! 変数の型は変更できません。 mutable = true; // 変数はシャドーイングで上書きできます。 let mutable = true; }
こちらも参照:
リテラルと演算子
整数の1、浮動小数点数の1.2、文字の'a'、文字列の"abc"、真偽値のtrue、
そしてユニット型の()などは、リテラルで表すことができます。
整数は0x、0o、0bなどのプレフィックスをつけることで、
16進数、8進数、2進数でも表すことができます。
また、可読性のため、数値にアンダースコアを挟むことができます。 例えば
1_000は1000と同じで、0.000_001は0.000001と同じです。
コンパイラにリテラルの型を教えてあげなければいけません. 現在は、
u32サフィックスでリテラル32ビット符号なし整数になり、i32サフィックス
で32ビット符号付き整数になります。
Rustで使える演算子や、その優先順位は、Cのような言語 と似ています。
fn main() { // 整数の足し算 println!("1 + 2 = {}", 1u32 + 2); // 整数の引き算 println!("1 - 2 = {}", 1i32 - 2); // TODO ^ `1i32`を`1u32`に変えてみて、型の重要さを実感してみてください。 // 真偽値の短絡評価 println!("true AND false is {}", true && false); println!("true OR false is {}", true || false); println!("NOT true is {}", !true); // 真でないものは{} // ビット演算 println!("0011 AND 0101 is {:04b}", 0b0011u32 & 0b0101); println!("0011 OR 0101 is {:04b}", 0b0011u32 | 0b0101); println!("0011 XOR 0101 is {:04b}", 0b0011u32 ^ 0b0101); println!("1 << 5 is {}", 1u32 << 5); println!("0x80 >> 2 is 0x{:x}", 0x80u32 >> 2); // アンダースコアを使って可読性を上げましょう! println!("One million is written as {}", 1_000_000u32); // 100万は{}と書きます }
タプル
タプルは異なる型の値の集合です。タプルは()を使って表し、タプル自身の型は
T1、T2をメンバーの型だとして(T1, T2, ...)のように表します。タプルに
大きさの制限がないので、関数で複数の値を返すのにタプルが使われています。
// タプルは、関数の引数や、返り値としても使えます。 fn reverse(pair: (i32, bool)) -> (bool, i32) { // `let` can be used to bind the members of a tuple to variables let (integer, boolean) = pair; (boolean, integer) } // これは演習のための構造体です。 #[derive(Debug)] struct Matrix(f32, f32, f32, f32); fn main() { // 複数の異なる型の値を束ねたタプル let long_tuple = (1u8, 2u16, 3u32, 4u64, -1i8, -2i16, -3i32, -4i64, 0.1f32, 0.2f64, 'a', true); // タプルのインデックスを利用して、値を取り出すことができます。 println!("long tuple first value: {}", long_tuple.0); println!("long tuple second value: {}", long_tuple.1); // タプルはタプルのメンバーになれます。 let tuple_of_tuples = ((1u8, 2u16, 2u32), (4u64, -1i8), -2i16); // タプルは出力できます。 println!("tuple of tuples: {:?}", tuple_of_tuples); // ただし、長いタプルは出力できません。 // let too_long_tuple = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); // println!("too long tuple: {:?}", too_long_tuple); // TODO ^ コンパイルエラーを確認するために上の2行をアンコメントしてください。 let pair = (1, true); println!("pair is {:?}", pair); println!("the reversed pair is {:?}", reverse(pair)); // 一つしか要素がないタプルは、リテラルを括弧で囲ったものと // 区別するため、コンマが必要です。 println!("one element tuple: {:?}", (5u32,)); // 要素1つのタプル: {:?} println!("just an integer: {:?}", (5u32)); // ただの整数: {:?} //タプルを分解してそれぞれを別の変数に束縛する。 let tuple = (1, "hello", 4.5, true); let (a, b, c, d) = tuple; println!("{:?}, {:?}, {:?}, {:?}", a, b, c, d); let matrix = Matrix(1.1, 1.2, 2.1, 2.2); println!("{:?}", matrix); }
演習
-
復習:
fmt::Displayトレイトを上の例のMatrix構造体に実装し、 デバッグフォーマット{:?}をディスプレイフォーマット{}に変えたときに 以下のように表示されるようにしてください。( 1.1 1.2 ) ( 2.1 2.2 )ディスプレイの例に戻る必要があるかもしれません。
-
reverse関数をテンプレートとして、下のように、引数として Matrixを受け取り、2つの要素を交換したMatrixを返す関数transposeを追加 してください。println!("Matrix:\n{}", matrix); println!("Transpose:\n{}", transpose(matrix));出力結果:
Matrix: ( 1.1 1.2 ) ( 2.1 2.2 ) Transpose: ( 1.1 2.1 ) ( 1.2 2.2 )
配列とスライス
配列は、メモリ上に連続して保存される、同じ型Tの値の集合です。配列は角括弧[]
で作ることができます。また、長さはコンパイル時にわかっていなければならず、
[T; size]で型の定義ができます。
Slicesは配列と似ていますが、コンパイル時に長さが決まっていなくても良いです。
その代わり、データへのポインタと、スライスの長さという2つのデータを格納しなければ
なりません。1つのデータのサイズはアーキテクチャ依存(例えばx86-64なら64ビット)で、
usizeと同じ大きさです。
スライスは配列の一部分の借用として使うことができ、&[T]という型指定子が使えます。
use std::mem; // この関数はスライスを借用します。 fn analyze_slice(slice: &[i32]) { println!("first element of the slice: {}", slice[0]); println!("the slice has {} elements", slice.len()); } fn main() { // 固定長配列 (型シグネチャは余計です) let xs: [i32; 5] = [1, 2, 3, 4, 5]; // すべての要素を同じ値で初期化することができます。 let ys: [i32; 500] = [0; 500]; // インデックスは0から始まります。 println!("first element of the array: {}", xs[0]); // 配列の最初の要素: {} println!("second element of the array: {}", xs[1]); // 配列の2つ目の要素: {} // `len`は配列の長さを返します。 println!("array size: {}", xs.len()); // 配列はスタック上に置かれます。 println!("array occupies {} bytes", mem::size_of_val(&xs)); // 配列は{}バイト占有しています。 // 配列は自動的にスライスとして借用されます。 println!("borrow the whole array as a slice"); // 配列の全体をスライスとして借用する analyze_slice(&xs); // スライスで配列の部分を指す // [スタート..エンド]のようにして指定します。 // ただし、スタートはスライスの最初の位置のインデックス // エンドは1より大きいスライスの最後の位置のインデックスです。 println!("borrow a section of the array as a slice"); // 配列の部分をスライスとして借用する。 analyze_slice(&ys[1 .. 4]); // 範囲外の位置を指定するとコンパイルエラーが起こる。 println!("{}", xs[5]); }
カスタム型
Rustのカスタムデータ型は主にこの2つのキーワードで作ることができます。
struct: 構造体を定義するenum: 列挙体を定義する
定数はconstやstaticキーワードで作ることができます。
構造体
structキーワードで作ることができる構造体には3つのタイプがあります。
- タプル構造体。基本的な名前がついたタプル。
- 古典的なCの構造体
- ユニット構造体。フィールドを持たず、ジェネリックを扱うときに有用です。
#[derive(Debug)] struct Person<'a> { // 'aでライフタイムを定義しています。 name: &'a str, age: u8, } // ユニット構造体 struct Unit; // タプル構造体 struct Pair(i32, f32); // 2個のフィールドを持つ構造体 struct Point { x: f32, y: f32, } // 構造体は構造体のフィールドとして再利用できます。 #[allow(dead_code)] struct Rectangle { // 長方形は左上と右下の位置で定義できます。 // corners are in space. top_left: Point, bottom_right: Point, } fn main() { // 構造体を作って簡潔に初期化する。 let name = "Peter"; let age = 27; let peter = Person { name, age }; // 構造体のデバッグプリント println!("{:?}", peter); // `Point`をインスタンス化する let point: Point = Point { x: 10.3, y: 0.4 }; // Pointのフィールドにアクセスする println!("point coordinates: ({}, {})", point.x, point.y); // アップデート構文で他の構造体のフィールドを再利用して新しい構造体を作る。 let bottom_right = Point { x: 5.2, ..point }; // `point`からフィールドを再利用したため、`bottom_right.y`は`point.y`と // 同じになります。 println!("second point: ({}, {})", bottom_right.x, bottom_right.y); // `let`でpointを分割代入して束縛する let Point { x: top_edge, y: left_edge } = point; let _rectangle = Rectangle { // 構造体のインスタンス化も式です。 top_left: Point { x: left_edge, y: top_edge }, bottom_right: bottom_right, }; // ユニット構造体のインスタンス化 let _unit = Unit; // タプル構造体のインスタンス化 let pair = Pair(1, 0.1); // タプル構造体のフィールドにアクセスする println!("pair contains {:?} and {:?}", pair.0, pair.1); // タプル構造体を分割代入 let Pair(integer, decimal) = pair; println!("pair contains {:?} and {:?}", integer, decimal); }
演習
- 長方形の面積を計算する
rect_area関数を追加してください。(ネスト分割代入を試してみてください。) Pointとf32を引数にとり、Pointを左上の点、f32を一辺の長さとして正方形を作成してRectangleとして返す関数squareを追加してください。
こちらも参照:
Enum
enumはいくつかの型から1つ選ぶような型を作るときに使う。構造体の定義を満たすものは
すべてenum内で使える。
// Webのイベントを分類するのに`enum`を使う。名前と型情報を // 合わせて列挙子を定義することに注意してください。 // `PageLoad != PageUnload`で、`KeyPress(char) != Paste(String)`です。 // それぞれは異なっていて、独立です。 enum WebEvent { // `enum`は`unit`や、 PageLoad, PageUnload, // タプル構造体や、 KeyPress(char), Paste(String), // cライク構造体のようにできます。 Click { x: i64, y: i64 }, } // `WebEvent` enumを引数としてとり、何も返さない関数。 fn inspect(event: WebEvent) { match event { WebEvent::PageLoad => println!("page loaded"), WebEvent::PageUnload => println!("page unloaded"), // `enum`内の`c`を分割代入する。 WebEvent::KeyPress(c) => println!("pressed '{}'.", c), WebEvent::Paste(s) => println!("pasted \"{}\".", s), // `Click`を`x`と`y`に分割代入する。 WebEvent::Click { x, y } => { println!("clicked at x={}, y={}.", x, y); }, } } fn main() { let pressed = WebEvent::KeyPress('x'); // `to_owned()`は文字列のスライスから所有権を持つ`String`を作成します。 let pasted = WebEvent::Paste("my text".to_owned()); let click = WebEvent::Click { x: 20, y: 80 }; let load = WebEvent::PageLoad; let unload = WebEvent::PageUnload; inspect(pressed); inspect(pasted); inspect(click); inspect(load); inspect(unload); }
型エイリアス
型エイリアスを使うと、そのエイリアスからenumの列挙子を参照できます。 これはenumの名前が長過ぎたり、汎用的すぎたりして、名前を変えたいときに 使えるかもしれません。
enum VeryVerboseEnumOfThingsToDoWithNumbers { Add, Subtract, } // 型エイリアスを作る type Operations = VeryVerboseEnumOfThingsToDoWithNumbers; fn main() { // エイリアスを通してenumを参照できます。 // 長くも不便でもないです。 let x = Operations::Add; }
これの最も一般的な使い道は、implブロック内でSelfエイリアスとして使われるときです。
enum VeryVerboseEnumOfThingsToDoWithNumbers { Add, Subtract, } impl VeryVerboseEnumOfThingsToDoWithNumbers { fn run(&self, x: i32, y: i32) -> i32 { match self { Self::Add => x + y, Self::Subtract => x - y, } } }
enumと型エイリアスについてもっと知りたければ、この機能がRustの安定版に導入されたときの 安定化レポートを読んでください。
こちらも参照:
use
use宣言を使うと、変数のスコープを絶対名で指定する必要がなくなります。
// 使われていないコードの警告をなくす属性 #![allow(dead_code)] enum Status { Rich, Poor, } enum Work { Civilian, Soldier, } fn main() { // 明示的に名前を指定して`use`して、 // 絶対名で指定しなくても使用できるようになる。 use crate::Status::{Poor, Rich}; // `Work`の中の名前をすべて自動的に`use`する。 use crate::Work::*; // `Status::Poor`と等しい。 let status = Poor; // `Work::Civilian`と等しい。 let work = Civilian; match status { // 上で`use`しているので、スコープは不要です。 Rich => println!("The rich have lots of money!"), // 富豪はたくさんの金を持っています! Poor => println!("The poor have no money..."), // 貧民は金を持っていません... } match work { // これも同じです。 Civilian => println!("Civilians work!"), // 民間人は働きます! Soldier => println!("Soldiers fight!"), // 兵士は戦います! } }
こちらも参照:
CライクなEnum
enumはCライクにも使えます。
// 使用されていないコードの警告をなくす属性 #![allow(dead_code)] // 値を明示しない場合、0から順に入る。 enum Number { Zero, One, Two, } // 明確な指定をするenum enum Color { Red = 0xff0000, Green = 0x00ff00, Blue = 0x0000ff, } fn main() { // `enums`は整数にキャストできる。 println!("zero is {}", Number::Zero as i32); println!("one is {}", Number::One as i32); println!("roses are #{:06x}", Color::Red as i32); // バラは#{:06x} println!("violets are #{:06x}", Color::Blue as i32); // スミレは#{:06x} }
こちらも参照:
テストケース: 連結リスト
enumsは連結リストを作るのに適当です。
use crate::List::*; enum List { // Cons: 要素と次のノードを指すポインタからなるタプル構造体 Cons(u32, Box<List>), // Nil: 連結リストの終わりを指すノード Nil, } // enumに関連付けられた関数 impl List { // 空のリストを作る。 fn new() -> List { // `Nil`は`List`型を持つ Nil } // リストを受け取り、前に要素を追加したものを返す。 fn prepend(self, elem: u32) -> List { // `Cons`も`List`型を持つ。 Cons(elem, Box::new(self)) } // Listの長さを返す fn len(&self) -> u32 { // このメソッドの振る舞いは`self`の列挙子によって変化するため、 // matchを使う必要があります。 // `self`の型は`&List`なので、`*self`の型は`List`になる。match // するときは参照`&T`より実体`T`を使う方が好ましい。 match *self { // `self`は借用なので、tailの所有権は取れない。 // 代わりにtailの参照を取る。 Cons(_, ref tail) => 1 + tail.len(), // 空リストの長さは0 Nil => 0 } } // Listを(ヒープ上の)文字列として返す。 fn stringify(&self) -> String { match *self { Cons(head, ref tail) => { // `format!`は`print!`に似ているが、コンソールに出力せず、 // ヒープ上の文字列を返す。 format!("{}, {}", head, tail.stringify()) }, Nil => { format!("Nil") }, } } } fn main() { // 空リストを作る let mut list = List::new(); // 要素をいくつか追加する list = list.prepend(1); list = list.prepend(2); list = list.prepend(3); // 最終的なリストの状態を見る println!("linked list has length: {}", list.len()); println!("{}", list.stringify()); }
こちらも参照:
定数
Rustには2つの異なるタイプの定数があり、グローバルスコープを含むすべての場所で 宣言できます。どちらも明示的な型注釈が必要です。
const: 変更できない値(普通はこっち)。static:mutにすることができる'staticライフタイムを持つ変数。 'staticライフタイムであることは推論されるので、明示的に指定しなくても良い。 可変なstatic変数にアクセス、変更することはunsafeです。
// グローバル変数はあらゆるスコープの外で定義します。 static LANGUAGE: &str = "Rust"; const THRESHOLD: i32 = 10; // THRESHOLD: しきい値 fn is_big(n: i32) -> bool { // 関数内から定数を参照 n > THRESHOLD } fn main() { let n = 16; // main関数で定数にアクセス println!("This is {}", LANGUAGE); println!("The threshold is {}", THRESHOLD); println!("{} is {}", n, if is_big(n) { "big" } else { "small" }); // エラー: `const`は変更できません。 THRESHOLD = 5; // FIXME ^ この行をコメントアウトしてください }
こちらも参照:
変数束縛
Rustは静的型付けによる型安全性を提供しています。宣言時に型注釈をつけることもできますが、 ほとんどの場合、注釈の労力を軽減するため、コンパイラが変数の型を文脈から推論することが できます。
(リテラルのような)値は、letを使って変数に束縛することができます。
fn main() { let an_integer = 1u32; let a_boolean = true; let unit = (); // `an_integer`を`copied_integer`にコピーする let copied_integer = an_integer; println!("An integer: {:?}", copied_integer); println!("A boolean: {:?}", a_boolean); println!("Meet the unit value: {:?}", unit); // コンパイラは、使われていない変数の束縛に対して警告をします。 // 変数名の最初にアンダースコアを付けることによって無効化できます。 let _unused_variable = 3u32; let noisy_unused_variable = 2u32; // FIXME ^ 警告を消すため、アンダースコアを付けてください。 }
可変性
変数束縛はデフォルトで不変ですが、mut修飾子で上書きできます。
fn main() { let _immutable_binding = 1; let mut mutable_binding = 1; println!("Before mutation: {}", mutable_binding); // 変更する前: {} // Ok mutable_binding += 1; println!("After mutation: {}", mutable_binding); // 変更した後: {} // エラー! _immutable_binding += 1; // FIXME ^ この行をコメントアウトしてください。 }
コンパイラは可変性に関するエラーに対して詳細な診断をします。
スコープとシャドーイング
変数束縛は、スコープを持ち、それはブロック内で有効です。ブロックは、
波括弧{}でくくられた文の集合のことです。さらに、変数のシャドーイング
も許可されています。
fn main() { // この束縛はmain関数内で有効です。 let long_lived_binding = 1; // これはブロックで、main関数より小さいスコープを持っています。 { // このブロック内でのみ有効。 let short_lived_binding = 2; println!("inner short: {}", short_lived_binding); // 外側の束縛を*覆い隠す*ことができます。 let long_lived_binding = 5_f32; println!("inner long: {}", long_lived_binding); } // ブロックの終わり // エラー! `short_lived_binding`はこのスコープに存在しません。 println!("outer short: {}", short_lived_binding); // FIXME ^ この行をコメントアウトしてください。 println!("outer long: {}", long_lived_binding); // この束縛も前の束縛を*覆い隠し*ます。 let long_lived_binding = 'a'; println!("outer long: {}", long_lived_binding); }
宣言
変数を最初に宣言して、後で初期化することができます。 しかし、初期化されていない変数を使う危険性があるので、めったに 使いません。
fn main() { // 変数束縛の宣言 let a_binding; { let x = 2; // 束縛の初期化 a_binding = x * x; } println!("a binding: {}", a_binding); let another_binding; // エラー! 初期化されていない変数を使用しています。 println!("another binding: {}", another_binding); // FIXME ^ この行をコメントアウトする another_binding = 1; println!("another binding: {}", another_binding); }
未定義な動作をする危険があるので、コンパイラは初期化されていない変数の使用を禁止しています。
フリーズ
データが同じ名前の不変な変数に覆い隠された時、これもフリーズします。 フリーズされたデータは、不変な束縛がスコープを外れるまで、変更できません。
fn main() { let mut _mutable_integer = 7i32; { // 不変な`_mutable_integer`で覆い隠す let _mutable_integer = _mutable_integer; // エラー! `_mutable_integer`はこのスコープではフリーズされています。 _mutable_integer = 50; // FIXME ^ この行をコメントアウトする // `_mutable_integer`がスコープを出る } // Ok! このスコープでは`_mutable_integer`はフリーズされていません。 _mutable_integer = 3; }
型
Rustは、プリミティブやユーザー定義型を変更、定義できるいくつかのメカニズム を提供しています。以下の節でカバーします。
キャスト
Rustはプリミティブ型の暗黙的な変換(強制)を提供していません。しかし、
asキーワードで明示的な変換(キャスト)ができます。
整数型の変換に関する規則はCと基本的に同じですが、Cには未定義動作があり、 Rustはすべての型キャストがうまく定義されています。
// キャストでの桁溢れに関する警告をなくす。 #![allow(overflowing_literals)] fn main() { let decimal = 65.4321_f32; // エラー! 暗黙的な変換はしません let integer: u8 = decimal; // FIXME ^ この行をコメントアウトしてください // 明示的な変換 let integer = decimal as u8; let character = integer as char; // エラー! これは変換規則によって禁止されています。浮動小数点は文字に直接変換できません。 let character = decimal as char; // FIXME ^ この行をコメントアウトしてください println!("Casting: {} -> {} -> {}", decimal, integer, character); // 値を符号なし整数型Tにキャストするときは、T::MAX + 1 // を新しい型にフィットするように加減します。 // 1000はu16にフィットしています。 println!("1000 as a u16 is: {}", 1000 as u16); // 1000 - 256 - 256 - 256 = 232 // 内部時には、最初の8最下位ビット(LSB)は保存され、 // 残りの最上位ビットは切り捨てられます。 println!("1000 as a u8 is : {}", 1000 as u8); // -1 + 256 = 255 println!(" -1 as a u8 is : {}", (-1i8) as u8); // 正の数は、剰余と同じです。 println!("1000 mod 256 is : {}", 1000 % 256); // 符号付き整数型に変換する時、(ビット処理)結果は対応する符号なし整数型への // キャストの結果と同じです。最上位ビットが1の時、その値は負です。 // もちろん、これはすでにフィットしています。 println!(" 128 as a i16 is: {}", 128 as i16); // 128 as u8 -> 128 これの8ビットでの2の補数は println!(" 128 as a i8 is : {}", 128 as i8); // 上の例を繰り返します。 // 1000 as u8 -> 232 println!("1000 as a u8 is : {}", 1000 as u8); // 232の2の補数は-24 println!(" 232 as a i8 is : {}", 232 as i8); }
リテラル
数値リテラルはサフィックスで型の注釈が付けられます。例として、
42というリテラルの型をi32であると指定するには、42i32と書きます。
サフィックスされていない数値リテラルの型は、それががどう使われるかによります。もし
制約がなければ、コンパイラは整数にi32を、浮動小数点数にf64を使います。
fn main() { // サフィックスされたリテラル。これらの型は初期化時に特定されます。 let x = 1u8; let y = 2u32; let z = 3f32; // サフィックスされていないリテラル。どのように使われるかによって型が決まる。 let i = 1; let f = 1.0; // `size_of_val` は変数のバイト数を返します。 println!("size of `x` in bytes: {}", std::mem::size_of_val(&x)); println!("size of `y` in bytes: {}", std::mem::size_of_val(&y)); println!("size of `z` in bytes: {}", std::mem::size_of_val(&z)); println!("size of `i` in bytes: {}", std::mem::size_of_val(&i)); println!("size of `f` in bytes: {}", std::mem::size_of_val(&f)); }
このコードにはまだ説明していないいくつかのまだ説明していない概念が使われています。 ここで短気な読者のために簡単に説明します。
std::mem::size_of_valは関数ですが、フルパスで呼び出されています。コードは モジュールと呼ばれる論理ユニットで分割できます。ここでは、size_of_val関数はmemモジュールで定義され、memモジュールはstdクレートで定義されています。 詳細は、モジュールとクレートを見てください。
推論
型推論システムはとても賢いです。初期化時の式の値の型だけでなく、 その変数が後でどのように使われるかによって型を推論します。 ここに型推論についての高度な例があります。
fn main() { // 注釈によって、コンパイラは`elem`の型がu8であることを知ります。 let elem = 5u8; // 空のベクター(可変長配列)を作ります。 let mut vec = Vec::new(); // この時点では、コンパイラは`vec`の型を正確には知りません。 // なにかのベクター(`Vec<_>`)であることだけわかっています。 // `elem`をベクターにプッシュします。 vec.push(elem); // ああ! 今、コンパイラは`vec`が`u8`のベクター(`Vec<u8>`)であることを知りました。 // TODO ^ `vec.push(elem)`行をコメントアウトしてみてください。 println!("{:?}", vec); }
型の注釈が必要なくなると、コンパイラもプログラマも幸せになれます!
エイリアシング
type文は存在する型に新しい名前を付けるのに使えます。型はUpperCamelCase
(単語のはじめが大文字)の名前を持つ必要があり、そうでないとコンパイラは警告
を出します。ただし、usize、f32などのプリミティブ型は例外です。
// `NanoSecond`は`u64`の新しい名前です。 type NanoSecond = u64; type Inch = u64; // 警告が出ないように属性を付けます。 #[allow(non_camel_case_types)] type u64_t = u64; // TODO ^ 属性を消してみてください。 fn main() { // `NanoSecond` = `Inch` = `u64_t` = `u64`. let nanoseconds: NanoSecond = 5 as u64_t; let inches: Inch = 2 as u64_t; // 型エイリアスは新しい型ではないので、余分な型安全性を提供しない // ことに注意してください。 println!("{} nanoseconds + {} inches = {} unit?", nanoseconds, inches, nanoseconds + inches); }
エイリアスは主に冗長性を軽減するために使われます。例えば、IoResult<T>型は
Result<T, IoError>型のエイリアスです。
こちらも参照
変換
プリミティブ型はキャストで変換できます。
Rustはカスタム型(structとenumのこと)の変換をトレイトを使って
処理しています。汎用的な変換はFromトレイトやIntoトレイトを使って行われます。
しかし、 特にStringへ、Stringからの変換など、より一般的なケースのため、より
具体的な変換が必要になることがあります。
FromとInto
FromトレイトとIntoトレイとは本質的にリンクされていて、これは実際には
その実装の一部です。もし型Aから型Bに変換できたら、型Bから型Aに変換できると
信じるのは簡単でしょう。
From
Fromトレイトで、他の型からどのように自分自身を作ることができるかを定義できます。
これによって、いくつかの型と変換するとてもシンプルなメカニズムを提供しています。
標準ライブラリに、一般的な型とプリミティブ型を変換するための、このトレイトの
数多くの実装があります。
例として、strはStringに簡単に変換できます。
#![allow(unused_variables)] fn main() { let my_str = "hello"; let my_string = String::from(my_str); }
同じように、独自の型に対して実装できます。
use std::convert::From; #[derive(Debug)] struct Number { value: i32, } impl From<i32> for Number { fn from(item: i32) -> Self { Number { value: item } } } fn main() { let num = Number::from(30); println!("My number is {:?}", num); }
Into
Intoトレイトは単純にFromトレイトの逆です。もしFromトレイトが
あなたの型に実装されていたら、Intoは必要に応じてこれを呼び出します。
コンパイラはほとんどの場合変換する型を決めることができないので、Into
トレイトを使うには、それを指定する必要があります。しかし、これは自由に
機能が得られることを考えれば小さなトレードオフです。
use std::convert::From; #[derive(Debug)] struct Number { value: i32, } impl From<i32> for Number { fn from(item: i32) -> Self { Number { value: item } } } fn main() { let int = 5; // 型の宣言を削除してみてください let num: Number = int.into(); println!("My number is {:?}", num); }
TryFromとTryInto
SFromとIntoに似ていますが、TryFromとTryIntoは
型を変換するための汎用的なトレイトです。From/Intoとは違い、TryFrom/
TryIntoは、失敗するかもしれない変換ができ、Resultを返します。
use std::convert::TryFrom; use std::convert::TryInto; #[derive(Debug, PartialEq)] struct EvenNumber(i32); impl TryFrom<i32> for EvenNumber { type Error = (); fn try_from(value: i32) -> Result<Self, Self::Error> { if value % 2 == 0 { Ok(EvenNumber(value)) } else { Err(()) } } } fn main() { // TryFrom assert_eq!(EvenNumber::try_from(8), Ok(EvenNumber(8))); assert_eq!(EvenNumber::try_from(5), Err(())); // TryInto let result: Result<EvenNumber, ()> = 8i32.try_into(); assert_eq!(result, Ok(EvenNumber(8))); let result: Result<EvenNumber, ()> = 5i32.try_into(); assert_eq!(result, Err(())); }
Stringsから変換、Stringに変換
Stringに変換
ある型からStringに変換するには、その型にToStringトレイトを実装するだけで
良いです。 直接実装するより、print!節で説明したように、ToStringを
自動的に提供するfmt::Displayトレイトを実装した方が良いです。
use std::fmt; struct Circle { radius: i32 } impl fmt::Display for Circle { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Circle of radius {}", self.radius) } } fn main() { let circle = Circle { radius: 6 }; println!("{}", circle.to_string()); }
Stringを解析する
文字列から変換される最も一般的な型は数値型です。慣用的なアプローチは、
parse関数を使い、型推論か「ターボフィッシュ(turbofish)」構文を
使って型を指定することです。両方の例が以下に紹介されています。
これで文字列をその型にFromStrトレイトが実装されている限り、指定した型に変換することが
できます。これは標準ライブラリ内の数多くの型で実装できます。この機能をユーザー定義型に
追加するには、単純にFromStrトレイトを実装すればよいだけです。
fn main() { let parsed: i32 = "5".parse().unwrap(); let turbo_parsed = "10".parse::<i32>().unwrap(); let sum = parsed + turbo_parsed; println!("Sum: {:?}", sum); }
式
Rustプログラムは(ほとんど)文の集合でできています。
fn main() {
// 文
// 文
// 文
}
Rustの文にはいくつかの種類があります。最も一般的な2つは変数束縛と、
;を使った式です。
fn main() {
// 変数束縛
let x = 5;
// 式;
x;
x + 1;
15;
}
ブロックも式です。なので、値として扱うことができます。その場合、
ブロックの最後の式がローカル変数のような場所を表す式に代入されます。
ただし、ブロックの最後の式がセミコロンで終わる場合は、返り値は()に
なります。
fn main() { let x = 5u32; let y = { let x_squared = x * x; let x_cube = x_squared * x; // This expression will be assigned to `y` x_cube + x_squared + x }; let z = { // セミコロンがあるので、`z`には`()`が代入されます。 2 * x; }; println!("x is {:?}", x); println!("y is {:?}", y); println!("z is {:?}", z); }
フロー制御
if/else、forなどのフロー制御は、あらゆるプログラミング言語にとって
重要な要素です。Rustのフロー制御について見ていきましょう。
if/else
if-elseでの条件分岐は他の言語とほぼ同じです。多くの言語と違って、
条件を括弧で囲む必要はありません。また、条件の後にはブロックが続きます。
if-elseは式の一種であり、すべての枝が同じ型を返す必要があります。
fn main() { let n = 5; if n < 0 { print!("{} is negative", n); // {}は負です } else if n > 0 { print!("{} is positive", n); // {}は正です } else { print!("{} is zero", n); // {}はゼロです } let big_n = if n < 10 && n > -10 { println!(", and is a small number, increase ten-fold"); // そして、小さいので、10倍します // この式は`i32`を返します。 10 * n } else { println!(", and is a big number, halve the number"); // そして、大きいので、半分にします // この式は`i32`を返す必要があります n / 2 // TODO ^ この式にセミコロンを付けてみてください。 }; // ^ ここにセミコロンを付けるのを忘れないでください! すべての`let`文に必要です。 println!("{} -> {}", n, big_n); }
loop
Rustは、無限ループを作るloopキーワードを提供しています。
break文でいつでもループを抜けることができ、continue文で残りの
部分を飛ばして次の繰り返しに進むことができます。
fn main() { let mut count = 0u32; println!("Let's count until infinity!"); // 無限まで数えましょう! // 無限ループ loop { count += 1; if count == 3 { println!("three"); // 残りの処理を飛ばして次に行く。 continue; } println!("{}", count); if count == 5 { println!("OK, that's enough"); // OK, これで十分 // このループを抜ける break; } } }
ネストとラベル
ネストされたループを扱っている時、外側のループをbreak、continue できます。
その時は、ループはラベル'labelで注釈されている必要があり、break/continue文
にラベルを渡さなければいけません。
#![allow(unreachable_code)] fn main() { 'outer: loop { println!("Entered the outer loop"); // 外側のループに入りました 'inner: loop { println!("Entered the inner loop"); // 内側のループに入りました // 内側のループだけから抜けます //break; // 外側のループを抜けます break 'outer; } println!("This point will never be reached"); // ここは決して実行されません } println!("Exited the outer loop"); // 外側のループを出ました }
ループの返り値
loopの使い道の一つは成功するまでオペレーションを繰り返すことです。オペレーション
が値を返す時、それを残りのコードに渡す必要があるかもしれません。
それをbreakに渡せば、loop式から値が返されます。
fn main() { let mut counter = 0; let result = loop { counter += 1; if counter == 10 { break counter * 2; } }; assert_eq!(result, 20); }
while
whileキーワードは条件が真である間ループするのに使います。
悪名高きFizzBuzzをwhileループで書いてみましょう。
fn main() { // カウンタ変数 let mut n = 1; // `n`が101より小さい間ループする while n < 101 { if n % 15 == 0 { println!("fizzbuzz"); } else if n % 3 == 0 { println!("fizz"); } else if n % 5 == 0 { println!("buzz"); } else { println!("{}", n); } // カウンタをインクリメントする n += 1; } }
forループ
forとrange
for in文はイテレータを使って繰り返し処理をするのに使います。
イテレータを作る最も簡単な方法の一つは、a..bのように書くことです。
これはa(含む)からb(除く)までの数値を順に産出(yield)します。
whileの代わりにforを使ってFizzBuzzを書いてみましょう。
fn main() { // `n`はそれぞれの繰り返しで1, 2, ..., 100の値をとります for n in 1..101 { if n % 15 == 0 { println!("fizzbuzz"); } else if n % 3 == 0 { println!("fizz"); } else if n % 5 == 0 { println!("buzz"); } else { println!("{}", n); } } }
代わりにa..=bで両端を含むことができます。
上の例は次のように書けます。
fn main() { // `n`はそれぞれの繰り返しで1, 2, ..., 100の値をとります for n in 1..=100 { if n % 15 == 0 { println!("fizzbuzz"); } else if n % 3 == 0 { println!("fizz"); } else if n % 5 == 0 { println!("buzz"); } else { println!("{}", n); } } }
forとイテレータ
for in文はイテレータといくつかの方法で相互作用することができます。
イテレータトレイトの節で議論したように、デフォルトでforループは
コレクションのinto_iter関数を実行しますが、これだけがコレクションを
イテレータに変換するわけではありません。
into_iter、iter、iter_mutはその中のデータに対する異なる見方を使って、
違った方法でコレクションをイテレータに変換します。
iter- これは、コレクションの各要素を各イテレーションで借用します。 コレクション変更しないため、ループの後に再利用できます。
fn main() { let names = vec!["Bob", "Frank", "Ferris"]; for name in names.iter() { match name { &"Ferris" => println!("There is a rustacean among us!"), // 私達のようなRustaceanがいます _ => println!("Hello {}", name), } } }
into_iter- これはコレクションを消費し、各繰り返しで正確なデータを提供します。 一度コレクションが消費されると、データを「move」し、もはや再利用できないようになります。
fn main() { let names = vec!["Bob", "Frank", "Ferris"]; for name in names.into_iter() { match name { "Ferris" => println!("There is a rustacean among us!"), _ => println!("Hello {}", name), } } }
iter_mut- コレクションの各要素を可変的に借用し、コレクションをその場で変更できる ようにします。
fn main() { let mut names = vec!["Bob", "Frank", "Ferris"]; for name in names.iter_mut() { *name = match name { &mut "Ferris" => "There is a rustacean among us!", _ => "Hello", } } println!("names: {:?}", names); }
上記の例ではmatch分岐の型が、繰り返しのタイプの違いを示すキーになります。
タイプの違いによって、違った振る舞いができることが分かるでしょう。
こちらも参照:
match
RustはCのswitchのような、matchキーワードによる条件分岐を提供しています。
fn main() { let number = 13; // TODO ^ `number`を違う値にして試してみてください。 println!("Tell me about {}", number); // {}について教えて match number { //一つの値にマッチする 1 => println!("One!"), // いくつかの値にマッチする 2 | 3 | 5 | 7 | 11 => println!("This is a prime"), // 最後の値を含むrangeにマッチする 13..=19 => println!("A teen"), // Handle the rest of cases _ => println!("Ain't special"), } let boolean = true; // Matchも式です let binary = match boolean { // matchのアームはとりうるすべての値を網羅しなければなりません。 false => 0, true => 1, // TODO ^ 上のどちらかをコメントアウトしてしてください。 }; println!("{} -> {}", boolean, binary); }
分割代入
matchブロックで、は様々な方法でアイテムを分割代入できます。
タプル
タプルはmatch内で以下のように分割代入できます。
fn main() { let pair = (0, -2); // TODO ^ `pair`を違う値にして試してみてください println!("Tell me about {:?}", pair); // {:?}について教えて // matchはタプルの分割代入に使えます。 match pair { // 2つ目を分割代入する (0, y) => println!("First is `0` and `y` is `{:?}`", y), // 最初は`0`で`y`は`{:?}`です (x, 0) => println!("`x` is `{:?}` and last is `0`", x), // `x`は`{:?}`で最後は`0`です _ => println!("It doesn't matter what they are"), // これが何なのかわかりません // `_` は値を変数として束縛しないことを意味します。 } }
こちらも参照:
enum
enumも似たように分割代入できます。
// 列挙子を一つしか使わないので、警告を消すために // `allow`が必要です。 #[allow(dead_code)] enum Color { // この3つは名前だけで扱います。 Red, Blue, Green, // これらは、カラーモデルと言って、`u32`タプルを併せて扱います。 RGB(u32, u32, u32), HSV(u32, u32, u32), HSL(u32, u32, u32), CMY(u32, u32, u32), CMYK(u32, u32, u32, u32), } fn main() { let color = Color::RGB(122, 17, 40); // TODO ^ `color`の違う列挙子で試してみてください。 println!("What color is it?"); // `enum`は`match`を使って分割代入できます。 match color { Color::Red => println!("The color is Red!"), Color::Blue => println!("The color is Blue!"), Color::Green => println!("The color is Green!"), Color::RGB(r, g, b) => println!("Red: {}, green: {}, and blue: {}!", r, g, b), Color::HSV(h, s, v) => println!("Hue: {}, saturation: {}, value: {}!", h, s, v), // 色相: {}, 彩度: {}, 明度: {} Color::HSL(h, s, l) => println!("Hue: {}, saturation: {}, lightness: {}!", h, s, l), // 色相: {}, 彩度: {}, 輝度: {} Color::CMY(c, m, y) => println!("Cyan: {}, magenta: {}, yellow: {}!", c, m, y), Color::CMYK(c, m, y, k) => println!("Cyan: {}, magenta: {}, yellow: {}, key (black): {}!", c, m, y, k), // すべての列挙子を使っったので、もう1つのアームは必要ありません。 } }
こちらも参照:
ポインタ/参照
Rustのポインタは、Cのような言語とは違い、間接参照と分割代入を区別する
必要があります。
- 間接参照には
*を使う - 分割代入には
&、ref、ref mutを使う
fn main() { // `i32`の参照を代入する。`&`で参照であることを // 明示している。 let reference = &4; match reference { // `reference`が`&val`にマッチした時、次の2つが // 比較されている。 // `&i32` // `&val` // ^ よって`&`が外れると、`i32`が // `val`に代入される。 &val => println!("Got a value via destructuring: {:?}", val), // 分割代入で値を取得しました: {:?} } // `&`をなくしたいときは、マッチする前に間接参照する match *reference { val => println!("Got a value via dereferencing: {:?}", val), // 間接参照で値を取得しました: {:?} } // 参照を代入しない場合はどうでしょうか? `reference`は右辺値が`&`で // 始まっていたので参照でしたが、これは参照ではありません。 let _not_a_reference = 3; // Rustはちょうどこの場合のために`ref`を提供しています。要素の参照が // 作られ、それが代入されます。 let ref _is_a_reference = 3; // 同様に、参照を使わずに値を定義し、`ref`や`ref mut`で参照 // を取得することができます。 let value = 5; let mut mut_value = 6; // `ref`キーワードで参照を作りましょう match value { ref r => println!("Got a reference to a value: {:?}", r), // 値の参照を取得しました } // 同様に`ref mut`を使います。 match mut_value { ref mut m => { // 参照を取得して、値を変更するには間接参照する必要がある。 *m += 10; println!("We added 10. `mut_value`: {:?}", m); // 10加えました。`mut_value`: {:?} }, } }
構造体
同様に、structも以下のように分割代入できます。
fn main() { struct Foo { x: (u32, u32), y: u32, } // 値を変更して、何が起こるか見てみましょう。 let foo = Foo { x: (1, 2), y: 3 }; match foo { Foo { x: (1, b), y } => println!("First of x is 1, b = {}, y = {} ", b, y), // xの最初は1, b = {}, y = {} // 構造体を分割代入して、変数を改名することができます。 // 順番は関係ありません Foo { y: 2, x: i } => println!("y is 2, i = {:?}", i), // yは2, i = {:?} // いくつかの変数を無視することができます。 Foo { y, .. } => println!("y = {}, we don't care about x", y), // y = {}, xについてはわかりません // `x`に言及していないため、エラーになります。 //Foo { y } => println!("y = {}", y), } }
こちらも参照:
ガード
matchではアームをふるい分けするのにガードが使えます。
fn main() { let pair = (2, -2); // TODO ^ `pair`を違う値にしてみてください。 println!("Tell me about {:?}", pair); // {:?}についておしえて match pair { (x, y) if x == y => println!("These are twins"), // これらは対です // ^ `if 条件`がガードとして働きます (x, y) if x + y == 0 => println!("Antimatter, kaboom!"), // ドドーン! (x, _) if x % 2 == 1 => println!("The first one is odd"), // はじめの要素が奇数です _ => println!("No correlation..."), // 相関がありません } }
こちらも参照:
束縛
変数を直接マッチさせないときは、分割代入なしで自分自身を参照
できません。matchは自分自身を束縛する@記号を提供しています。
// `age`関数は`u32`を返します fn age() -> u32 { 15 } fn main() { println!("Tell me what type of person you are"); // あなたがどんな人かおしえて match age() { 0 => println!("I'm not born yet I guess"), // 多分私はまだ生まれていません。 // 1 ..= 12にはマッチできますが、この子供の歳がわかりません。 // 代わりに、1 ..= 12の数値を束縛する変数nを用意します。 // これで歳がわかります。 n @ 1 ..= 12 => println!("I'm a child of age {:?}", n), // 私は{:?}歳の子供です n @ 13 ..= 19 => println!("I'm a teen of age {:?}", n), // 私は{:?}歳のティーン(訳注: 10代の若者)です。 // マッチしなかった場合の処理 n => println!("I'm an old person of age {:?}", n), } }
Optionのように、「分割代入」したenumの列挙子にも使えます。
fn some_number() -> Option<u32> { Some(42) } fn main() { match some_number() { // `Some`列挙子で、値が42に等しかったときに、マッチして // `n`に束縛する。 Some(n @ 42) => println!("The Answer: {}!", n), // 答え: {}! // 他の数値にマッチする。 Some(n) => println!("Not interesting... {}", n), // 面白くない... {} // 他のもの(`None`列挙子)にマッチする。 _ => (), } }
こちらも参照:
if let
enumをマッチする時、しばしばmatchは冗長です。例えば
#![allow(unused_variables)] fn main() { // `Option<i32>`型の`optional`を作成する let optional = Some(7); match optional { Some(i) => { println!("This is a really long string and `{:?}`", i); // これは本当に長い文字列で、`{:?}`です // ^ iをoptionから分割代入しているので、2つインデントが必要です。 }, _ => {}, // ^ `match`はすべてを網羅しないといけないため、必要です。 // 無駄だと思いませんか? }; }
if letはこの場合おいて簡潔な上、失敗時の処理も柔軟にできます。
fn main() { // すべて`Option<i32>`型です let number = Some(7); let letter: Option<i32> = None; let emoticon: Option<i32> = None; // `if let`は、「もし`let`が`number`を分解した // 結果が`Some(i)`なら、ブロックを実行する。」 // という意味です。 if let Some(i) = number { println!("Matched {:?}!", i); // {:?}にマッチしました! } // もし失敗を扱いたければ、elseを使います。 if let Some(i) = letter { println!("Matched {:?}!", i); } else { // 分解に失敗した時、このブロックを実行します。 println!("Didn't match a number. Let's go with a letter!"); // 数字にマッチしませんでした。文字で行きましょう! } // もう一つ失敗したときの処理を作る let i_like_letters = false; if let Some(i) = emoticon { println!("Matched {:?}!", i); // 失敗した時、`else if`の条件を見て、分岐します。 } else if i_like_letters { println!("Didn't match a number. Let's go with a letter!"); } else { // 条件がfalseの時、この枝を実行します。 println!("I don't like letters. Let's go with an emoticon :)!"); // 文字は嫌いです。絵文字で行きましょう :) } }
同じように、if letはすべてのenumで使えます。
// 例で使うenum enum Foo { Bar, Baz, Qux(u32) } fn main() { // 例で使う変数 let a = Foo::Bar; let b = Foo::Baz; let c = Foo::Qux(100); // 変数aはFoo::Barにマッチする if let Foo::Bar = a { println!("a is foobar"); } // 変数bはFoo::Barにマッチしないので、 // 何も出力しない。 if let Foo::Bar = b { println!("b is foobar"); } // 変数cは値を持つFoo::Quxにマッチし、 // 前の例のSome()と同じような挙動をする。 if let Foo::Qux(value) = c { println!("c is {}", value); } // `if let`でも束縛は使えます。 if let Foo::Qux(value @ 100) = c { println!("c is one hundred"); } }
もう一つのif letの恩恵は、enumのパラメータなしの列挙子にマッチできることです。enumはPartialEqを実装も継承もしないため、enumのインスタンスは比較できず、
if Foo::Bar == a はコンパイルに失敗しますが、 if letで同じことができます。
挑戦しますか? 次の例をif letを使うように修正してください。
// このenumはPartialEqを実装も継承もしません。 // よって、下のFoo::Bar == aは失敗します。 enum Foo {Bar} fn main() { let a = Foo::Bar; // 変数aはFoo::Barにマッチします if Foo::Bar == a { // ^-- この節はコンパイルエラーを起こします。代わりに`if let`を使ってください。 println!("a is foobar"); } }
こちらも参照:
while let
if letと同様に、while letも冗長なmatch文を簡略化するのに使えます。
iをインクリメントする次の節を見てください。
#![allow(unused_variables)] fn main() { // `Option<i32>`型の`optional`を作ります let mut optional = Some(0); // この試行を繰り返します。 loop { match optional { // `optional`がSome(i)にマッチすれば、このブロックを実行します。 Some(i) => { if i > 9 { println!("Greater than 9, quit!"); // 0より大きいので、終了します! optional = None; } else { println!("`i` is `{:?}`. Try again.", i); // `i`は`{:?}`です。もう1度試してください。 optional = Some(i + 1); } // ^ 3インデント必要です! }, // マッチしなければ、ループを出ます。 _ => { break; } // ^ これは必要でしょうか? もっと良い方法があります! } } }
while letを使えば、この節をより良くなります。
fn main() { // `Option<i32>`型の`optional`を作ります let mut optional = Some(0); // 「`let`が`optional`を`Some(i)`に分解できる限り、ブロック(`{}`)を実行し、 // そうでなければ`break`する」という意味 while let Some(i) = optional { if i > 9 { println!("Greater than 9, quit!"); optional = None; } else { println!("`i` is `{:?}`. Try again.", i); optional = Some(i + 1); } // ^ インデントも少なく、明示的な失敗の処理も不要です。 } // ^ `if let`は`else`/`else if`節を持っていますが、 // `while let`にはありません。 }
こちらも参照:
関数
関数はfnキーワードで宣言できます。引数の型は変数のように注釈し、
関数が値を返すときは、その型を矢印->の後に指定する必要があります。
関数内の最後の式が返されます。また、return文でループやifの中など、
関数の途中で値を返すことができます。
FizzBuzzを関数を使って書き直してみましょう!
// C/C++と違い、関数定義の順番に決まりはないです。 fn main() { // ここで関数を使い、あとで宣言することができます。 fizzbuzz_to(100); } // 真偽値を返す関数。 fn is_divisible_by(lhs: u32, rhs: u32) -> bool { // コーナーケースでは、関数が終わる前に値を返します。 if rhs == 0 { return false; } // これは式であり、ここには`return`キーワードが必要ありません。 lhs % rhs == 0 } // 値を返さない関数です。実際にはユニット型`()`を返しています。 fn fizzbuzz(n: u32) -> () { if is_divisible_by(n, 15) { println!("fizzbuzz"); } else if is_divisible_by(n, 3) { println!("fizz"); } else if is_divisible_by(n, 5) { println!("buzz"); } else { println!("{}", n); } } // 関数が`()`を返す時、返り値の型は省略できます。 fn fizzbuzz_to(n: u32) { for n in 1..n + 1 { fizzbuzz(n); } }
メソッド
メソッドはオブジェクトに関連付けられた関数です。メソッドはselfキーワードで
オブジェクトのデータや、他のメソッドにアクセスできます。メソッドはimpl
ブロック内で定義します。
struct Point { x: f64, y: f64, } // 実装ブロックです。`Point`のすべてのメソッドはここで定義されます。 impl Point { // これは静的メソッドです。 // 静的メソッドは、インスタンスから呼び出す必要はなく、 // 一般的にコンストラクタとして使われます。 fn origin() -> Point { Point { x: 0.0, y: 0.0 } } // 2つの引数をとるもう一つの静的メソッドです。 fn new(x: f64, y: f64) -> Point { Point { x: x, y: y } } } struct Rectangle { p1: Point, p2: Point, } impl Rectangle { // これはインスタンスメソッドです。 // `&self`は`self: &Self`の糖衣構文であり、`Self`は呼び出し元の // オブジェクトの型です。ここでは`Self`は`Rectangle`と等価です。 fn area(&self) -> f64 { // ドット演算子で`self`のフィールドにアクセスできます。 let Point { x: x1, y: y1 } = self.p1; let Point { x: x2, y: y2 } = self.p2; // `abs`は`f64`のメソッドで、呼び出し元の絶対値を返します。 ((x1 - x2) * (y1 - y2)).abs() } fn perimeter(&self) -> f64 { let Point { x: x1, y: y1 } = self.p1; let Point { x: x2, y: y2 } = self.p2; 2.0 * ((x1 - x2).abs() + (y1 - y2).abs()) } // このメソッドは呼び出し元を可変にする必要があります。 // `&mut self`は`self: &mut Self`の糖衣構文です。 fn translate(&mut self, x: f64, y: f64) { self.p1.x += x; self.p2.x += x; self.p1.y += y; self.p2.y += y; } } // `Pair`は2つのヒープ上に割り当てられた整数を持ちます。 struct Pair(Box<i32>, Box<i32>); impl Pair { // このメソッドは呼び出し元オブジェクトのリソースを「消費」します。 // `self`は`self: Self`の糖衣構文です。 fn destroy(self) { // `self`を分割代入する。 let Pair(first, second) = self; println!("Destroying Pair({}, {})", first, second); // Pair({}, {})を破壊しました。 // `first`と`second`はスコープを出て、開放されます。 } } fn main() { let rectangle = Rectangle { // 静的メソッドはコロン2つで呼び出します。 p1: Point::origin(), p2: Point::new(3.0, 4.0), }; // インスタンスメソッドはドット演算子で呼び出します。つまり、 // 第一引数`&self`は暗示的に渡されます。`rectangle.perimeter()`は // `Rectangle::perimeter(&rectangle)`と同等です。 println!("Rectangle perimeter: {}", rectangle.perimeter()); println!("Rectangle area: {}", rectangle.area()); let mut square = Rectangle { p1: Point::origin(), p2: Point::new(1.0, 1.0), }; // エラー! `rectangle`は不変ですが、メソッドはオブジェクトが可変である // ことを要求しています。 //rectangle.translate(1.0, 0.0); // TODO ^ この行をアンコメントしてみてください。 // OK! 可変な変数は可変なメソッドを呼び出せます。 square.translate(1.0, 1.0); let pair = Pair(Box::new(1), Box::new(2)); pair.destroy(); // エラー! 前の`destroy`で`pair`を「消費」しました。 //pair.destroy(); // TODO ^ この行をアンコメントしてください。 }
クロージャ
Rustのクロージャ(ラムダ式やラムダとも呼ばれる)は環境をキャプチャできる関数です。 例えば、このクロージャはxをキャプチャします。
|val| val + x
クロージャの構文と機能はその場での使用に便利です。関数を呼ぶようにクロージャ を呼び出すことができます。しかし、入力値と返り値の型は推論できますが、入力の変数名は 指定する必要があります。
クロージャの他の特徴としては
- 入力変数名を
()ではなく||で囲む。 - 式が一つのときは
{}で囲む必要はありません(他は必須です)。 - 外部の変数をキャプチャできます。.
fn main() { // 関数とクロージャでのインクリメント fn function (i: i32) -> i32 { i + 1 } // クロージャは名無しなので、参照に束縛します。 // 注釈は関数の注釈と同じですが、本体を`{}`で囲む必要は // ありません。名無しのクロージャはは呼び出すために // 変数に代入する必要があります。 let closure_annotated = |i: i32| -> i32 { i + 1 }; let closure_inferred = |i | i + 1 ; let i = 1; // 関数とクロージャを呼び出す。 println!("function: {}", function(i)); println!("closure_annotated: {}", closure_annotated(i)); println!("closure_inferred: {}", closure_inferred(i)); // 引数をとらず、`i32`を返すクロージャ。 // 返り値は推論されます。 let one = || 1; println!("closure returning one: {}", one()); }
キャプチャ
クロージャは柔軟で、型注釈がなくても動きます。これのおかげで、 場合に応じて所有権をもらったり借用したりすることができます。 クロージャは変数を捕捉(キャプチャ)できます。
- 参照として(
&T) - 可変参照として(
&mut T) - 値として(
T)
クロージャははじめ参照を取得しようとし、場合に応じて他のものも取得します。
fn main() { use std::mem; let color = String::from("green"); // `color`を借用(`&`)して出力するクロージャを作り、借用と共に変数 // `print`に保存します。`print`が最後に使われるまで借用は続きます。 // `println!`は不変参照があれば機能するので、これ以上なにかする必要はない。 let print = || println!("`color`: {}", color); // 借用を使ったクロージャを呼び出す。 print(); // クロージャは不変な借用しか保持していないため、 // `color`はもう1度借用できます。 let _reborrow = &color; print(); // `print`の最後の使用のあとでのみ所有権を渡せます。 let _color_moved = color; let mut count = 0; // `count`をインクリメントするクロージャは`&mut count`または`count`を // 必要としますが、`&mut count`で事足りるのでそれを使います。よって // `count`を借用します。 // // 内部に可変な型があるので、`inc`には`mut`が必要です。 // 可変なクロージャは呼び出す度に内部変数を変更します。 let mut inc = || { count += 1; println!("`count`: {}", count); }; // 可変借用を使ったクロージャを呼ぶ。 inc(); // あとで呼び出されるため、クロージャはまだ`count`を可変借用しています。 // なので、もう1度借用とするとエラーになります。 // let _reborrow = &count; // ^ TODO: この行をアンコメントしてみてください。 inc(); // クロージャはもう使われないので、`&mut count`を借用する必要はありません。 // なので、エラーなく再借用できます。 let _count_reborrowed = &mut count; // コピーできない型 let movable = Box::new(3); // `mem::drop`は`T`を必要とするので、値そのものが必要である。 // コピーできる型はコピーし、できない型は値をそのまま移動させます。 let consume = || { println!("`movable`: {:?}", movable); mem::drop(movable); }; // `consume`は変数を消費するため、一度しか呼べません。 consume(); // consume(); // ^ TODO: この行をアンコメントしてみてください。 }
バーティカルバー(|)の前にmoveを付けると、キャプチャした変数の
所有権をもらいます。
fn main() { // `Vec`はコピーできない型です。 let haystack = vec![1, 2, 3]; let contains = move |needle| haystack.contains(needle); println!("{}", contains(&1)); println!("{}", contains(&4)); // println!("There're {} elements in vec", haystack.len()); // ^ ボローチェッカーは移動させた後の変数の再利用を禁止しているので、 // 上をアンコメントすると、コンパイルエラーになります。 // `move`をクロージャから削除すると、クロージャは変数haystackを不変借用するので、 // haystackはまだ使え、上をアンコメントしてもエラーしなくなります。 }
こちらも参照:
引数として
Rustでその場で変数をキャプチャするときは型注釈を省略しましたが、関数を書くときは
この曖昧さは許されません。クロージャを引数として受け取るときは、いくつかの
トレイトを使います。制限の少ない順に、以下の通りです。
Fn: 参照(&T)をキャプチャするクロージャFnMut: 可変参照(&mut T)をキャプチャするクロージャFnOnce: 値(T)をキャプチャするクロージャ
コンパイラはできる限り制限が最小になるように変数をキャプチャします。
例えば、引数はFnOnceを注釈していたとしたら、クロージャは&T、&mut T、T
をキャプチャする可能性がありますが、コンパイラはクロージャがどのように
使われているかに応じてこれを決定します。
なぜなら、値の移動ができる時、どのタイプの借用もできるからです。
その逆はできないことに注意してください。引数がFnを注釈していた場合、
&mut TやTのキャプチャはできません。
次の例で、Fn、FnMut、FnOnceを入れ替えて何が起こるか試してください。
// クロージャを引数として取り、呼び出す関数 // <F>はFが「ジェネリック型の引数」であることを表します。 fn apply<F>(f: F) where // このクロージャは引数を取らず、何も返しません F: FnOnce() { // ^ TODO: `Fn`や`FnMat`に変えてみてください。 f(); } // クロージャをとり、`i32`をとる関数 fn apply_to_3<F>(f: F) -> i32 where // `i32`をとり`i32`を取るクロージャ F: Fn(i32) -> i32 { f(3) } fn main() { use std::mem; let greeting = "hello"; // コピーできない型 // `to_owned`は借用したデータから自分のデータを作成するのに使います。 let mut farewell = "goodbye".to_owned(); // `greeting`という参照と`farewell`という値の2つを // キャプチャする let diary = || { // `greeting`は参照なので`Fn`が必要です。 println!("I said {}.", greeting); // {}と言った。 // `farewell`は変更が必要なので可変参照として // キャプチャする。ここで`FnMut`が必要になる。 farewell.push_str("!!!"); println!("Then I screamed {}.", farewell); // そして{}と叫んだ。 println!("Now I can sleep. zzzzz"); // いま寝る。zzzzz // `farewell`を手動でDropするため、値としてキャプチャする。 // ここで`FnOnce`が必要となる。 mem::drop(farewell); }; // クロージャを実行する関数を呼び出す apply(diary); // `double`は`apply_to_3`のトレイトの要件を満たす let double = |x| 2 * x; println!("3 doubled: {}", apply_to_3(double)); }
こちらも参照:
型匿名性
クロージャで簡潔にスコープから変数をキャプチャできます。なにか注意する点は あるのでしょうか? もちろんです。クロージャを引数として取る時、ジェネリック を使わなくてはいけません。
#![allow(unused_variables)] fn main() { // `F`はジェネリックである必要がある。 fn apply<F>(f: F) where F: FnOnce() { f(); } }
クロージャが作られた時、コンパイラはキャプチャした変数を保存する匿名の
、Fn、FnMut、FnOnceの3つのトレイトの内一つをその型に対して実装した
構造体を作成し、呼び出されるまで待ちます。
この構造体は型が未指定なため、関数での使用にはジェネリックが必要です。
しかし、型パラメータ<T>を指定するだけではまだ曖昧です。そのため、Fn、
FnMut、FnOnceのどれを実装しているか明示する必要があります。
// `F`は`Fn`を実装している必要があり、クロージャは何もとらず何も返さない。 // `print`にはこれが正しい。 fn apply<F>(f: F) where F: Fn() { f(); } fn main() { let x = 7; // `x`を匿名の型としてキャプチャし、それに // `Fn`を実装する。これを`print`に格納する。 let print = || println!("{}", x); apply(print); }
こちらも参照:
入力関数
クロージャが引数として使えることから、関数も同じようにできないのかと思った人も いるかもしれません。その通り、できます! クロージャを引数に取る関数を定義したら、 そのクロージャのトレイト境界を満たすすべての関数を引数として渡すことができます。
// `Fn`を境界としたジェネリック型`F`を引数に取る // 関数を定義し、渡されたクロージャを即座に呼び出す。 fn call_me<F: Fn()>(f: F) { f(); } // `Fn`境界を満たす関数を定義する。 fn function() { println!("I'm a function!"); } fn main() { // `Fn`境界を満たすクロージャを定義する。 let closure = || println!("I'm a closure!"); call_me(closure); call_me(function); }
クロージャの捕捉について詳しく見たいときは、Fn、FnMut、FnOnce
トレイトのドキュメントを参照してください。
こちらも参照:
返り値として
クロージャを引数として取ることができるのなら、クロージャを返す
こともできるはずです。しかし、匿名なクロージャの型は、定義時点では
不明です。そのため、クロージャを返すにはimpl Traitを使う必要が
あります。
クロージャを返すのに使われるトレイトは以下の通りです。
FnFnMutFnOnce
キャプチャしている値をすべて渡すため、moveキーワードが必要です。これがないと
変数は関数終了と同時にdropされ、クロージャ内のキャプチャしている参照が無効に
なります。
fn create_fn() -> impl Fn() { let text = "Fn".to_owned(); move || println!("This is a: {}", text) } fn create_fnmut() -> impl FnMut() { let text = "FnMut".to_owned(); move || println!("This is a: {}", text) } fn create_fnonce() -> impl FnOnce() { let text = "FnOnce".to_owned(); move || println!("This is a: {}", text) } fn main() { let fn_plain = create_fn(); let mut fn_mut = create_fnmut(); let fn_once = create_fnonce(); fn_plain(); fn_mut(); fn_once(); }
こちらも参照:
stdでの例
この節ではstdライブラリ内でのクロージャの使用例をいくつか紹介します。
Iterator::any
Iterator::anyはイテレータ内に一つでも条件を満たす要素があればtrueを、
なければfalseを返すメソッドです。
pub trait Iterator {
// イテレータ内の要素の型。
type Item;
// `any`は`&mut self`をとるため、呼び出し元を借用し、変更するかも
// しれませんが、消費はしません。
fn any<F>(&mut self, f: F) -> bool where
// `FnMut`はクロージャがキャプチャした値を変更するかもしれないが、
// 消費はしないことを表します。`Self::Item`はクロージャに
// 値を引数として渡すことを意味します。
F: FnMut(Self::Item) -> bool {}
}
fn main() { let vec1 = vec![1, 2, 3]; let vec2 = vec![4, 5, 6]; // ベクターの`iter()`は`&i32`を産出するので、`i32`に分割代入する必要があります。 println!("2 in vec1: {}", vec1.iter() .any(|&x| x == 2)); // ベクターの`into_iter()`は`i32`を産出するので、分割代入は必要ありません。 println!("2 in vec2: {}", vec2.into_iter().any(| x| x == 2)); let array1 = [1, 2, 3]; let array2 = [4, 5, 6]; // 配列の`iter()`は`&i32`を産出します。 println!("2 in array1: {}", array1.iter() .any(|&x| x == 2)); // 配列の`into_iter()`もなんと`&i32`を産出します。 println!("2 in array2: {}", array2.into_iter().any(|&x| x == 2)); }
こちらも参照:
イテレータを検索する
Iterator::findはイテレータ内で条件を満たす関数を検索する関数です。もし
一つも条件を満たさなかったときは、Noneを返します。型シグネチャは以下の
通りです。
pub trait Iterator {
// 要素の型
type Item;
// `find`は`&mut self`をとります。よって呼び出し元は借用され、変更される
// かもしれませんが、消費はされません。
fn find<P>(&mut self, predicate: P) -> Option<Self::Item> where
// `FnMut`はキャプチャする値が変更される可能性はあるが、
// 消費はされないことを表します。`&Self::Item`はクロージャに
// 参照を渡すことを意味します。
P: FnMut(&Self::Item) -> bool {}
}
fn main() { let vec1 = vec![1, 2, 3]; let vec2 = vec![4, 5, 6]; // ベクターの`iter()`は`&i32`を産出します。 let mut iter = vec1.iter(); // ベクターの`into_iter()`は`i32`を産出します。 let mut into_iter = vec2.into_iter(); // ベクターの`iter()`は`&i32`を産出します。要素の参照をとる必要があるので、 // `&&i32`を`i32`に分割代入する必要があります。 println!("Find 2 in vec1: {:?}", iter .find(|&&x| x == 2)); // `into_iter()`は`i32`を産出します。なので同様に、`&i32`を`i32` // に分割代入する必要があります。 println!("Find 2 in vec2: {:?}", into_iter.find(| &x| x == 2)); let array1 = [1, 2, 3]; let array2 = [4, 5, 6]; // 配列の`iter()`は`&i32`を産出します。 println!("Find 2 in array1: {:?}", array1.iter() .find(|&&x| x == 2)); // 配列の`into_iter()`も`&i32`を産出します。 println!("Find 2 in array2: {:?}", array2.into_iter().find(|&&x| x == 2)); }
Iterator::findは要素の参照を渡します。要素の_インデックス_がほしいときは
Iterator::positionを使ってください。
fn main() { let vec = vec![1, 9, 3, 3, 13, 2]; let index_of_first_even_number = vec.iter().position(|x| x % 2 == 0); assert_eq!(index_of_first_even_number, Some(5)); let index_of_first_negative_number = vec.iter().position(|x| x < &0); assert_eq!(index_of_first_negative_number, None); }
こちらも参照:
std::iter::Iterator::findstd::iter::Iterator::find_mapstd::iter::Iterator::positionstd::iter::Iterator::rposition
高階関数
Rustは高階関数(HOF)を提供しています。これは1つ以上の関数を引数にとることで、 より使いやすい関数を作る機能です。高階関数と遅延イテレータはRustの関数型機能です。
fn is_odd(n: u32) -> bool { n % 2 == 1 } fn main() { println!("Find the sum of all the squared odd numbers under 1000"); // 1000以下の奇数かつ平方数の和を求めます let upper = 1000; // 命令型のアプローチ // 蓄積用変数を定義する let mut acc = 0; // 0, 1, 2, ... と無限まで繰り返す for n in 0.. { // 数を2乗する let n_squared = n * n; if n_squared >= upper { // 上限を超えれば終了する break; } else if is_odd(n_squared) { // 奇数ならばaccに追加する acc += n_squared; } } println!("imperative style: {}", acc); // 命令型: {} // 関数型のアプローチ let sum_of_squared_odd_numbers: u32 = (0..).map(|n| n * n) // すべての自然数を2乗する .take_while(|&n_squared| n_squared < upper) // 上限まで .filter(|&n_squared| is_odd(n_squared)) // 奇数ならば .fold(0, |acc, n_squared| acc + n_squared); // 合計する println!("functional style: {}", sum_of_squared_odd_numbers); // 関数型: {} }
Option型とイテレータの実装には高階関数が使われています。
発散関数
発散関数は値を返しません。空の型である!を使って示してください。
#![allow(unused_variables)] fn main() { fn foo() -> ! { panic!("This call never returns."); // この呼び出しは決して返りません。 } }
他の方とは違い、この型はインスタンス化できません。この型には持ちうる値
がないからです。一つの値を持ちうる()型とは違うことに注意してください。
例えば、この関数は、返り値の型に関する情報がありませんが、 普通に値を返します。
fn some_fn() { () } fn main() { let a: () = some_fn(); println!("This function returns and you can see this line.") // この関数は値を返し、この行が出力されます。 }
この関数とは違い、呼び出し元に制御が戻ることはありません。
#![feature(never_type)]
fn main() {
let x: ! = panic!("This call never returns.");
println!("You will never see this line!"); // この行は出力されません。
}
これは、抽象的な概念に見えますが、実はとても有用で、手頃です。
この型の主な利点は、どんな型にもキャストでき、matchの枝など、
正確な型が必要なときに使えます。これを使って、このような関数が書けます。
fn main() { fn sum_odd_numbers(up_to: u32) -> u32 { let mut acc = 0; for i in 0..up_to { // このmatch式は、変数additionの型であるu32を返さなければ // いけないことに注意してください。 let addition: u32 = match i%2 == 1 { // 変数iは u32になるので、問題ありません。 true => i, // 一方、"continue"式はu32を返しませんが、何も返さないことは、 // match式の型の要件に違反しないので、これでも問題ありません。 false => continue, }; acc += addition; } acc } println!("Sum of odd numbers up to 9 (excluding): {}", sum_odd_numbers(9)); // 9未満の奇数の和: {} }
ネットワークサーバのように、永遠にループ(例えばloop {})する処理や、
プロセスを終了する関数(例えばexit())などに使われます。
モジュール
Rustは階層的に論理ユニット(モジュール)によってコードを分割する強力なモジュールシステムを 備えており、その間で可視性(public/private)を操作できます。
モジュールは関数、構造体、トレイト、implブロック、そして他のモジュール
などの集合体です。
可視性
デフォルトで、モジュールの要素はプライベートです。しかし、pub修飾子でこれを
上書きすることができます。パブリックな要素のみがモジュールスコープの外からアクセス
できます。
// `my_mod`というモジュールを定義する mod my_mod { // モジュールの要素はデフォルトでプライベートです。 fn private_function() { println!("called `my_mod::private_function()`"); } // `pub`修飾子を使って上書きできます。 pub fn function() { println!("called `my_mod::function()`"); } // モジュール内では、プライベートな要素を含むすべての // 他の要素にアクセスできます。 pub fn indirect_access() { print!("called `my_mod::indirect_access()`, that\n> "); private_function(); } // モジュールはネストできます pub mod nested { pub fn function() { println!("called `my_mod::nested::function()`"); } #[allow(dead_code)] fn private_function() { println!("called `my_mod::nested::private_function()`"); } // 関数を`pub(in パス)`構文を使って宣言すると、指定したパスからのみ // アクセスできます。`path`は自分の親か、先祖のモジュールでなければいけません。 pub(in crate::my_mod) fn public_function_in_my_mod() { print!("called `my_mod::nested::public_function_in_my_mod()`, that\n> "); public_function_in_nested(); } // 関数を`pub(self)`構文を使って宣言すると、現在のモジュールでのみ // アクセスでき、プライベートと同じになります。 pub(self) fn public_function_in_nested() { println!("called `my_mod::nested::public_function_in_nested()`"); } // 関数を`pub(super)`を構文を使って宣言すると、親のモジュールからのみ // アクセスできます。 pub(super) fn public_function_in_super_mod() { println!("called `my_mod::nested::public_function_in_super_mod()`"); } } pub fn call_public_function_in_my_mod() { print!("called `my_mod::call_public_function_in_my_mod()`, that\n> "); nested::public_function_in_my_mod(); print!("> "); nested::public_function_in_super_mod(); } // pub(crate)で関数が現在のクレート内でアクセスできるようになります。 pub(crate) fn public_function_in_crate() { println!("called `my_mod::public_function_in_crate()`"); } // ネストされたモジュールも同じ規則に従います。 mod private_nested { #[allow(dead_code)] pub fn function() { println!("called `my_mod::private_nested::function()`"); } // 親モジュールのプライベートな要素にもアクセスできます。 // 先祖のモジュールでも同じです。 #[allow(dead_code)] pub(crate) fn restricted_function() { println!("called `my_mod::private_nested::restricted_function()`"); } } } fn function() { println!("called `function()`"); } fn main() { // モジュールで同じ名前をもつ要素の明確化ができます。 function(); my_mod::function(); // ネストされたモジュールも含めて、パブリックな要素には // 親モジュールの外からでもアクセスできます。 my_mod::indirect_access(); my_mod::nested::function(); my_mod::call_public_function_in_my_mod(); // pub(crate)の要素は同じクレート内のどこからでもアクセスできます。 my_mod::public_function_in_crate(); // pub(in パス)の要素は指定したモジュールからのみアクセスできます。 // エラー! `public_function_in_my_mod`関数はプライベートです //my_mod::nested::public_function_in_my_mod(); // TODO ^ この行をアンコメントしてみてください // プライベートな要素は、パブリックなモジュールにネスト // されていたとしても、直接アクセスできません。 // エラー! `private_function`はプライベートです。 //my_mod::private_function(); // TODO ^ この行をアンコメントしてみてください // エラー! `private_function`はプライベートです //my_mod::nested::private_function(); // TODO ^ この行をアンコメントしてみてください // エラー! `private_nested`はプライベートモジュールです //my_mod::private_nested::function(); // TODO ^ この行をアンコメントしてみてください // エラー! `private_nested`はプライベートモジュールです //my_mod::private_nested::restricted_function(); // TODO ^ この行をアンコメントしてみてください }
構造体の可視性
構造体はフィールド内での可視性も持っています。デフォルトではプライベートで、
pub修飾子で上書きできます。この可視性は、定義されたモジュールの外からアクセスする
ときのみ有効で、情報を隠す(カプセル化する)ことを目的としています。
mod my { // ジェネリック型`T`のパブリックフィールドを持ったパブリックな構造体 pub struct OpenBox<T> { pub contents: T, } // ジェネリック型`T`のプライベートフィールドを持ったパブリックな構造体 #[allow(dead_code)] pub struct ClosedBox<T> { contents: T, } impl<T> ClosedBox<T> { // パブリックなコンストラクタメソッド pub fn new(contents: T) -> ClosedBox<T> { ClosedBox { contents: contents, } } } } fn main() { // パブリックフィールドを持ったパブリックな構造体はいつものように宣言できます。 let open_box = my::OpenBox { contents: "public information" }; // そしてフィールドにも普通にアクセスできます。 println!("The open box contains: {}", open_box.contents); // プライベートフィールドを持ったパブリックな構造体はフィールド名で宣言できません。 // エラー! `ClosedBox`にはプライベートフィールドがあります。 //let closed_box = my::ClosedBox { contents: "classified information" }; // TODO ^ この行をアンコメントしてみてください // しかし、パブリックなコンストラクタを使って初期化できます。 let _closed_box = my::ClosedBox::new("classified information"); // そしてプライベートなフィールドにはアクセスできません // エラー! `contents`フィールドはプライベートです //println!("The closed box contains: {}", _closed_box.contents); // TODO ^ この行をアンコメントしてみてください }
こちらも参照:
use宣言
use宣言は、アクセスを簡単にするため、パスに新しい名前を束縛します。
このように使います。
// extern crate deeply; // 普通、この行はコメントアウトされていません!
use crate::deeply::nested::{
my_first_function,
my_second_function,
AndATraitType
};
fn main() {
my_first_function();
}
違う名前に束縛してインポートするのにasキーワードを使います。
// `deeply::nested::function`を`other_function`に束縛する。 use deeply::nested::function as other_function; fn function() { println!("called `function()`"); } mod deeply { pub mod nested { pub fn function() { println!("called `deeply::nested::function()`"); } } } fn main() { // 簡単に`deeply::nested::function`にアクセスできる。 other_function(); println!("Entering block"); { // これは`use deeply::nested::function as function`と等価で、 // `function()`はもともとのものを覆い隠します。 use crate::deeply::nested::function; function(); // `use`束縛はローカルスコープを持っています。ここでは、 // `function()`のシャドーイングはこのブロック内だけで有効です。 println!("Leaving block"); } function(); }
superとself
super、selfキーワードはパス内での要素へのアクセスで曖昧さをなくし、
パスの不要なハードコードを防ぐのに使われます。
fn function() { println!("called `function()`"); } mod cool { pub fn function() { println!("called `cool::function()`"); } } mod my { fn function() { println!("called `my::function()`"); } mod cool { pub fn function() { println!("called `my::cool::function()`"); } } pub fn indirect_call() { // このスコープから`function`という名前のすべての関数にアクセスしましょう! print!("called `my::indirect_call()`, that\n> "); // `self`キーワードは現在のモジュール(ここでは`my`)を参照します。 // `self::function()`と`function()`は等価で、同じ結果を返します。 self::function(); function(); // `self`で`my`内の他のモジュールにもアクセスできます。 self::cool::function(); // `super`キーワードで親スコープ(ここでは`my`モジュールの外側)にアクセスできます。 super::function(); // ここで*crate*スコープの`cool::function`を束縛します。 // ここでcrateスコープとは一番外側のスコープのことです。 { use crate::cool::function as root_function; root_function(); } } } fn main() { my::indirect_call(); }
ファイル階層
モジュールはファイル/ディレクトリ階層で表すことができます。 可視性の例をファイルで再現してみましょう。
$ tree .
.
|-- my
| |-- inaccessible.rs
| |-- mod.rs
| `-- nested.rs
`-- split.rs
split.rsの中身:
// この宣言は`my.rs`または`my/mod.rs`を探索し、そこにあった要素を
// `my`モジュールとしてスコープに導入します。
mod my;
fn function() {
println!("called `function()`");
}
fn main() {
my::function();
function();
my::indirect_access();
my::nested::function();
}
my/mod.rsの中身:
// 同様に`mod inaccessible`と`mod nested`は`nested.rs`や`inaccessible.rs`ファイルを
// 探索し、そのモジュールを導入します。
mod inaccessible;
pub mod nested;
pub fn function() {
println!("called `my::function()`");
}
fn private_function() {
println!("called `my::private_function()`");
}
pub fn indirect_access() {
print!("called `my::indirect_access()`, that\n> ");
private_function();
}
my/nested.rsの中身:
pub fn function() {
println!("called `my::nested::function()`");
}
#[allow(dead_code)]
fn private_function() {
println!("called `my::nested::private_function()`");
}
my/inaccessible.rsの中身:
#[allow(dead_code)]
pub fn public_function() {
println!("called `my::inaccessible::public_function()`");
}
同じように動くか確かめましょう。
$ rustc split.rs && ./split
called `my::function()`
called `function()`
called `my::indirect_access()`, that
> called `my::private_function()`
called `my::nested::function()`
クレート
クレートはRustにおける編集ユニットです。rustc some_file.rsが呼び出された時、
some_file.rs はクレートファイルとして扱われます。もしsome_file.rs内にmod
宣言があれば、モジュールファイルの要素がコンパイラを実行する前に挿入されます。
言い換えれば、モジュールは独立してコンパイルされず、クレートのみがコンパイルされる
ということです。
クレートはバイナリまたはライブラリとしてコンパイルできます。デフォルトで、rustc
はクレートからバイナリを作ります。このふるまいは--crate-typeフラグにlibを立てる
ことによって上書きできます。
ライブラリ
ライブラリを作り、他のクレートにリンクしてみましょう。
pub fn public_function() {
println!("called rary's `public_function()`");
}
fn private_function() {
println!("called rary's `private_function()`");
}
pub fn indirect_access() {
print!("called rary's `indirect_access()`, that\n> ");
private_function();
}
$ rustc --crate-type=lib rary.rs
$ ls lib*
library.rlib
ライブラリは"lib"で始まる必要があります。デフォルトでクレートファイル
の名前にちなんで名付けられます。しかし、これはructcの--crate-name
オプションや、crate_name属性で上書きできます。
extern crate
クレートにライブラリをリンクするには、extern crate宣言が必要です。
これはライブラリをリンクするだけでなく、ライブラリのモジュールを
ライブラリ名と同じ名前でインポートします。可視性のルールはモジュール
だけでなくライブラリにも適用されます。
// `library`とリンクし、`rary`モジュールの要素をインポートします。
extern crate rary;
fn main() {
rary::public_function();
// エラー! `private_function`はプライベートです
//rary::private_function();
rary::indirect_access();
}
# library.rlibのようなコンパイルされたライブラリのパスは、
# 同じディレクトリにあることが想定されています。
$ rustc executable.rs --extern rary=library.rlib && ./executable
called rary's `public_function()`
called rary's `indirect_access()`, that
> called rary's `private_function()`
Cargo
cargoはRustの公式パッケージマネージャです。これはコードの質や開発速度を上げる
のに非常に役立ちます。Cargoには次のような機能があります。
- 依存管理とcrates.io(Rustの公式パッケージレジストリ) との連携
- ユニットテストの自動化
- ベンチマークの自動化
この章では基本的な使い方を説明します。包括的な情報はThe Cargo Book を参照してください。
依存
ほとんどのプログラムはライブラリへの依存を持ちます。もし手動で依存を管理したことがないなら、
その苦痛はわからないでしょう。幸運なことに、Rustのエコシステムはcargoで標準化されています!
cargoでプロジェクトの依存を管理できます。
新しいRustプロジェクトを作る:
# バイナリ
cargo new foo
# ライブラリ
cargo new --lib foo
この章では、バイナリを作ることを想定しています。しかし、コンセプト はすべて同じです。
上のコマンドを入力すると、次のようなファイル階層になるでしょう:
foo
├── Cargo.toml
└── src
└── main.rs
main.rsはソースファイルの根幹です。何も新しいことはありません。
Cargo.tomlはこのプロジェクト(foo)用のcargo設定ファイルです。
中身はこのようになっているでしょう:
[package]
name = "foo"
version = "0.1.0"
authors = ["mark"]
[dependencies]
[package]下のnameフィールドはプロジェクト名です。これはクレートを
crates.ioに公開するときに使われます。(詳しくは後で) これはコンパイルされた
バイナリの名前にもなります。
versionフィールドはセマンティックバージョニング
をベースとしたバージョン番号です。
authorsフィールドはクレートを公開するときの開発者のリストです。
[dependencies]節にプロジェクトの依存を記述します。
例えば、素晴らしいCLIツールを作りたいときは、crates.io (Rust
の公式パッケージレジストリ)にたくさんの素晴らしいクレートがあります。ポピュラーなものでは
clapなどが良いでしょう。
これを書いている時点で、clapの最新版は2.27.1です。 プログラムに依存を追加するには、
Cargo.tomlの[dependencies]節に、clap = "2.27.1"という行を加え、もちろんmain.rsに
extern crate clapを加える必要がありますが、それだけで良いです! clapをプログラム内で
使えるようになります。
cargoは他のタイプの依存もサポートしています。
こちらが小さなサンプルです。
[package]
name = "foo"
version = "0.1.0"
authors = ["mark"]
[dependencies]
clap = "2.27.1" # crates.ioから
rand = { git = "https://github.com/rust-lang-nursery/rand" } # オンラインリポジトリから
bar = { path = "../bar" } # ローカルファイルシステムから
cargoは依存マネージャ以外の機能も備えています。Cargo.tomlのすべての
設定オプションの一覧ははformat specificationにあります。
プロジェクトを実行ファイルにビルドするにはcargo buildをプロジェクトディレクトリ内の
いずれかのディレクトリ(サブディレクトリでもOKです!)から実行してください。また、
cargo runでビルド後に実行できます。これらのコマンドは、依存関係を解決し、
必要に応じてクレートをダウンロードし、すべてをビルドします。(makeのように、まだビルド
されていないもののみビルドします。)
たったこれだけです!
慣習
前の節で、このようなディレクトリ階層を見ました。
foo
├── Cargo.toml
└── src
└── main.rs
同じプロジェクト内でバイナリを2つ以上作るときはどうするのでしょうか?
cargoはこれについてもサポートしています。前に見たようにmainがデフォルトのバイナリですが、
bin/ディレクトリ内で他のバイナリを作ることができます。
foo
├── Cargo.toml
└── src
├── main.rs
└── bin
└── my_other_bin.rs
そのバイナリをコンパイル&実行したいときは、cargoにバイナリ名をmy_other_binとして
--bin my_other_binフラグを渡せばよいだけです。
複数のバイナリに加えて、cargoは、ベンチマーク、テスト、サンプルなど多くの機能
を備えています。
次の節で、テストについて見ていきます。
テスト
あらゆるソフトにとって、テストは重要です! Rustはテストについての サポートを提供しています。The Rust Programming Languageの(この章 を参照してください。
上のドキュメントでユニットテストと整合性テストの書き方について述べています。
ユニットテストや整合性テストはtests/ディレクトリに入れることになっています。
foo
├── Cargo.toml
├── src
│ └── main.rs
└── tests
├── my_test.rs
└── my_other_test.rs
tests内のそれぞれのファイルが別のテストです。
cargoはすべてのテストを自動的に実行する機能を提供しています!
$ cargo test
このような出力が得られるはずです:
$ cargo test
Compiling blah v0.1.0 (file:///nobackup/blah)
Finished dev [unoptimized + debuginfo] target(s) in 0.89 secs
Running target/debug/deps/blah-d3b32b97275ec472
running 3 tests
test test_bar ... ok
test test_baz ... ok
test test_foo_bar ... ok
test test_foo ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
パターンにマッチするテストのみを実行することもできます:
$ cargo test test_foo
$ cargo test test_foo
Compiling blah v0.1.0 (file:///nobackup/blah)
Finished dev [unoptimized + debuginfo] target(s) in 0.35 secs
Running target/debug/deps/blah-d3b32b97275ec472
running 2 tests
test test_foo ... ok
test test_foo_bar ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out
注意として、Cargoは複数のテストを並列的に実行することがあります。そのため、 すべてのテストがお互いに競合しないようにする必要があります。例えば、それぞれの テストがファイルに出力する時、違うファイル名に出力する必要があります。
ビルドスクリプト
時々、デフォルトのcargoのビルドでは不十分な場合があります。おそらくそれはcargoが
コンパイルを成功するのにコード生成、他のファイルのコンパイルなどを事前にしなければ
いけないときでしょう。この問題を解決するために、Cargoではビルドスクリプトが実行できます。
Cargo.tomlに以下のような行を加えると、ビルドスクリプトが追加できます。
[package]
...
build = "build.rs"
デフォルトでCargoはプロジェクトディレクトリのbuild.rsファイルがあれば、
実行します。
ビルドスクリプトの使い方
ビルドスクリプトは単純にはじめにコンパイルされ、パッケージ内の他のものをコンパイルする Rustファイルです。これによってクレートの事前要件を満たすことができます。
Cargoはここで指定された環境変数を入力として実行されるスクリプトをいくつか提供しています。
また、スクリプトは標準出力を介して出力し、target/debug/build/<pkg>/outputにすべての
出力が書き込まれます。さらに、cargo:で始まる行はCargoに解析され、パッケージをコンパイル
するときのひきすうとして使われます。
さらなる仕様やサンプルについてはCargoのドキュメントを参照してください。
属性
属性は、モジュール、クレート、またはその要素などに対して適用されるメタデータです。 このメタデータは次のような用途に使われます。
- 条件に合致するときのみコードをコンパイルする
- クレート名、バージョン、タイプ(バイナリかライブラリ)を指定する
- リント(警告)を無効化する
- コンパイラの機能を有効化する(マクロ、グロブ、インポートなど)
- 外部ライブラリにリンクする
- 関数をユニットテストとしてマークする
- 関数をベンチマークとしてマークする
クレート全体に属性を適用するときは、#![crate_attribute]構文を使い、
モジュールや要素に対して適用するときは、#[item_attribute]構文を使います
(!を忘れないでください)。
属性はこのような構文で引数を取ることができます:
#[attribute = "value"]#[attribute(key = "value")]#[attribute(value)]
属性はこのようにして複数の引数を取ることもできます:
#[attribute(value, value2)]
#[attribute(value, value2, value3,
value4, value5)]
dead_code
コンパイラは使っていない関数に対して警告する
dead_codeリントを提供します。これを無効化
するための属性があります。
fn used_function() {} // `#[allow(dead_code)]`は`dead_code`リントを無効化します #[allow(dead_code)] fn unused_function() {} fn noisy_unused_function() {} // FIXME ^ 警告を消すために属性を加えてください fn main() { used_function(); }
実用のプログラムでは使わない関数は取り除いたほうが良いです。しかし、このような サンプルでは自然なサンプルになるように所々でこの属性を使います。
クレート
crate_type属性でクレートがバイナリかライブラリか(そしてどのタイプのライブラリか)
を指定し、crate_name属性でクレート名を設定します。
しかし、crate_typeやcrate_nameは、Cargoを使う場合は使わない方が良いことに
注意してください。これとCargoがメジャーなツールであることから、実用でこれが使われる
ケースは限られています。
// このクレートはライブラリです #![crate_type = "lib"] // そしてライブラリ名は"rary"です #![crate_name = "rary"] pub fn public_function() { println!("called rary's `public_function()`"); } fn private_function() { println!("called rary's `private_function()`"); } pub fn indirect_access() { print!("called rary's `indirect_access()`, that\n> "); private_function(); }
crate_type属性を使えば、--crate-typeフラグをrustcに
渡す必要はもはやありません。
$ rustc lib.rs
$ ls lib*
library.rlib
cfg
条件付きコンパイルは次の2つのオペレータによって行われます。
cfg属性: 属性としての#[cfg(...)]cfg!マクロ: 真偽値式を使ったマクロcfg!(...)
後者は実行時にチェックされ、trueやfalseなどのリテラルも使えます。
どちらも適切な構文で記述する必要があります。
// この関数はターゲットOSがlinuxである場合のみコンパイルされます #[cfg(target_os = "linux")] fn are_you_on_linux() { println!("You are running linux!"); } // この関数はターゲットOSがlinuxで*ない*場合のみコンパイルされます #[cfg(not(target_os = "linux"))] fn are_you_on_linux() { println!("You are *not* running linux!"); } fn main() { are_you_on_linux(); println!("Are you sure?"); if cfg!(target_os = "linux") { println!("Yes. It's definitely linux!"); } else { println!("Yes. It's definitely *not* linux!"); } }
こちらも参照:
カスタマイズ
target_osなど、いくつかの条件はrustcによって提供されますが、ructcに
--cfgフラグを追加することで指定できるカスタム条件もあります。
#[cfg(some_condition)] fn conditional_function() { println!("condition met!"); } fn main() { conditional_function(); }
cfgフラグがないとどうなるか試してみてください。
cfgフラグを指定すると:
$ rustc --cfg some_condition custom.rs && ./custom
condition met!
ジェネリック
ジェネリックは違う型の間で同じ機能を提供するのに使う機構です。 これはコードの重複を減らすのに有用ですが、複雑な構文が必要になります。 ジェネリック型を使うにはきちんと機能するか最新の注意を払わなければ いけません。そして、型パラメータとしての使い方が最も単純で一般的です。
ジェネリック型の型パラメータはカギ括弧とキャメルケース(<Aaa, Bbb, ...>)
が使われます。「ジェネリックな型パラメータ」は普通<T>で表します。Rustでは、
「ジェネリック」には「ジェネリックな型パラメータ<T>を1つ以上受け入れるもの」という
意味もあります。ジェネリックな型パラメータを指定された場合それはジェネリック型になり、
それ以外はすべて具象型(非ジェネリック)として扱われます。
例として、あらゆる型Tの引数を受け取るジェネリック関数fooを定義します。
fn foo<T>(arg: T) { ... }
Tは<T>によってジェネリックな型パラメータとして指定されているので、(arg: T)のように
ジェネリック型として使うことができます。前にTが構造体として定義されていたとしても、
ジェネリックが優先されます。
次の例で、手を動かしながらジェネリックを学んでいきましょう。
// 具象型`A` struct A; // `Single`型の宣言では、前に`<A>`がないことと、`A`が具象型として定義されていることから、 // `Single`は具象型になります。 struct Single(A); // ^ `Single`内で初めて型`A`が出てくる。 // ここで、ジェネリックな型パラメータ`<T>`が出てくるので、`T`はジェネリック型になり、 // `SingleGen`もジェネリック型になります。`T`どんな型にもなり得るので、上で定義した // 型`A`も受け取れます。 fn main() { // `Single`は具象型で、`A`をとります。 let _s = Single(A); // `SingleGen<char>`型の変数`_char`を作り、 // `SingleGen('a')`で初期化します。 // ここで、`SingleGen`には明示的に型パラメータが与えられています。 let _char: SingleGen<char> = SingleGen('a'); // `SingleGen`型の変数には明示的に型パラメータを与えなくても良い。 let _t = SingleGen(A); // Uses `A` defined at the top. let _i32 = SingleGen(6); // Uses `i32`. let _char = SingleGen('a'); // Uses `char`. }
こちらも参照:
関数
同じことが関数でも言えます。つまり、<T>のように指定された型Tは
ジェネリックになります。
ジェネリックな関数を使う時は、返り値がジェネリック型であり、それをコンパイラ が推論できない場合、明示的な型パラメータが必要になります。
このように、明示的な型宣言を使って関数を呼び出すことができます。
fun::<A, B, ...>().
struct A; // 具象型`A` struct S(A); // 具象型`S` struct SGen<T>(T); // ジェネリック型`SGen` // 以下の関数はすべて変数の所有権をもらい、すぐにスコープを抜けるため、 // 変数を開放します。 // `S`型の引数`_s`を取る関数`reg_fn`を定義する。 // ジェネリックな関数ではないので、`<T>`を使わない。 fn reg_fn(_s: S) {} // `SGen<A>`型の引数`_s`を取る関数`gen_spec_t`を定義する。 // `A`はジェネリックな型パラメータとして定義されていないため、 // これはジェネリックではない。 fn gen_spec_t(_s: SGen<A>) {} // `SGen<i32>`型の引数`_s`を取る関数`gen_spec_i32`を定義する。 // `i32`はジェネリックではないので、この関数もジェネリックでない。 fn gen_spec_i32(_s: SGen<i32>) {} // `SGen<T>`型の引数`_s`をとる関数`generic`を定義する。 // `<T>`があるため、`SGen<T>`はジェネリックであり、この関数は`T`に対してジェネリックです。 fn generic<T>(_s: SGen<T>) {} fn main() { // 非ジェネリックな関数を使う reg_fn(S(A)); // 具象型 gen_spec_t(SGen(A)); // 型パラメータ`A`を暗示的に渡す。 gen_spec_i32(SGen(6)); // 型パラメータ`i32`を暗示的に渡す。 // 明示的に`char`を`generic()`の型パラメータとして渡す。 generic::<char>(SGen('a')); // 暗示的に`char`が`generic()`の型パラメータとなる。 generic(SGen('c')); }
こちらも参照:
実装
関数のように、メソッドの実装に関してもジェネリック型特有の記法が必要です。
#![allow(unused_variables)] fn main() { struct S; // 具象型`S` struct GenericVal<T>(T); // ジェネリック型`GenericVal` // GenericValを明示的に型パラメータを指定して実装する。 impl GenericVal<f32> {} // `f32`を指定する impl GenericVal<S> {} // 上で定義した`S`を指定する // ジェネリックにするには`<T>`が必要です。 impl<T> GenericVal<T> {} }
struct Val { val: f64, } struct GenVal<T> { gen_val: T, } // impl of Val impl Val { fn value(&self) -> &f64 { &self.val } } // ジェネリック型`T`に対するGenValの実装 impl<T> GenVal<T> { fn value(&self) -> &T { &self.gen_val } } fn main() { let x = Val { val: 3.0 }; let y = GenVal { gen_val: 3i32 }; println!("{}, {}", x.value(), y.value()); }
こちらも参照:
トレイト
もちろんトレイトもジェネリックにできます。自分自身を入力とした
ジェネリックなメソッドdropをもつDropトレイトをジェネリックで
再実装します。
// コピーできない型 struct Empty; struct Null; // `T`に対してジェネリックなトレイト trait DoubleDrop<T> { // 呼び出し元と`T`型の引数をとり、何もしないメソッド。 fn double_drop(self, _: T); } // `DoubleDrop<T>`をジェネリックな引数`T`と // 呼び出し元`U`に対して実装する。 impl<T, U> DoubleDrop<T> for U { // このメソッドは両方の所有権をとり、 // 両方開放する。 fn double_drop(self, _: T) {} } fn main() { let empty = Empty; let null = Null; // `empty`と`null`を開放する。 empty.double_drop(null); //empty; //null; // ^ TODO: これらの行をアンコメントしてみてください }
こちらも参照:
境界
ジェネリックを使っていると、型パラメータが特定の機能を持っていることを規定するために、
トレイトを境界として使うことがあります。例えば、次の例は変数をプリントするため、
型TがDisplayトレイトを実装していることを規定しています。つまり、TはDisplay
を実装していなければならないのです。
// `Display`トレイトを実装している型`T`に対してジェネリックな
// 関数`printer`を定義する。
fn printer<T: Display>(t: T) {
println!("{}", t);
}
境界はジェネリックをすべての型ではなく、あるトレイトを実装している 型だけに対して適用するためにあります。つまり
struct S<T: Display>(T);
// エラー! `Vec<T>`は`Display`を実装していません。
// この特殊化は失敗します。
let s = S(vec![1]);
境界のもう一つの効果は、境界となるトレイトに実装されているメソッドを使用できることです。 例えば
// プリントマーカー`{:?}`を実装するトレイト use std::fmt::Debug; trait HasArea { fn area(&self) -> f64; } impl HasArea for Rectangle { fn area(&self) -> f64 { self.length * self.height } } #[derive(Debug)] struct Rectangle { length: f64, height: f64 } #[allow(dead_code)] struct Triangle { length: f64, height: f64 } // ジェネリック型`T`は`Debug`を実装している必要があります。型に関係なく、 // これは動作します。 fn print_debug<T: Debug>(t: &T) { println!("{:?}", t); } // `T`は`HasArea`を実装している必要があります。`HasArea`のメソッド`area` // を使用するための境界です。 fn area<T: HasArea>(t: &T) -> f64 { t.area() } fn main() { let rectangle = Rectangle { length: 3.0, height: 4.0 }; let _triangle = Triangle { length: 3.0, height: 4.0 }; print_debug(&rectangle); println!("Area: {}", area(&rectangle)); //print_debug(&_triangle); //println!("Area: {}", area(&_triangle)); // ^ TODO: これらをアンコメントしてみてください。 // | エラー: `Debug`または`HasArea`を実装していません。 }
さらに、読みやすくするため、場合によってはwhere節も
境界を適用するのに使われます。
こちらも参照:
テストケース: 空の境界
トレイトがもし何も機能を含んでいなくても、境界として使うことができます。
EqやCopyはstdライブラリ内でのそのようなトレイトの例です。
struct Cardinal; struct BlueJay; struct Turkey; trait Red {} trait Blue {} impl Red for Cardinal {} impl Blue for BlueJay {} // 以下の関数はトレイト境界を設けているが、そのトレイトが空かどうか // は関係ありません。 fn red<T: Red>(_: &T) -> &'static str { "red" } fn blue<T: Blue>(_: &T) -> &'static str { "blue" } fn main() { let cardinal = Cardinal; let blue_jay = BlueJay; let _turkey = Turkey; // 境界があるため、`red()`は`blue jay`に対して実行できない。 println!("A cardinal is {}", red(&cardinal)); println!("A blue jay is {}", blue(&blue_jay)); //println!("A turkey is {}", red(&_turkey)); // ^ TODO: この行をアンコメントしてみてください }
こちらも参照:
複数の境界
+で複数の境界が適用できます。複数の引数を受け取るときは、
通常通り,で分割します。
use std::fmt::{Debug, Display}; fn compare_prints<T: Debug + Display>(t: &T) { println!("Debug: `{:?}`", t); println!("Display: `{}`", t); } fn compare_types<T: Debug, U: Debug>(t: &T, u: &U) { println!("t: `{:?}`", t); println!("u: `{:?}`", u); } fn main() { let string = "words"; let array = [1, 2, 3]; let vec = vec![1, 2, 3]; compare_prints(&string); //compare_prints(&array); // TODO ^ この行をアンコメントしてみてください compare_types(&array, &vec); }
こちらも参照:
Where節
境界はwhere節を{の直前に置くことでも表現できます。
さらに、where節は境界を型パラメータだけでなく、任意の型に
適用することもできます。
場合によってはwhere節は有用です。
- ジェネリック型の指定と境界の指定を明確に分けたい時
impl <A: TraitB + TraitC, D: TraitE + TraitF> MyTrait<A, D> for YourType {}
// `where`節で境界を表現する
impl <A, D> MyTrait<A, D> for YourType where
A: TraitB + TraitC,
D: TraitE + TraitF {}
where節の方が通常の構文より表現力が高いときがあります。 この例のimplはwhere節を使わないと表現できません。
use std::fmt::Debug; trait PrintInOption { fn print_in_option(self); } // この例は他に`T: Debug`を使うか他の直接的でない方法を使うしかありません。 // これには`where`節が必要です。 impl<T> PrintInOption for T where Option<T>: Debug { // プリントされるものが`Option<T>`型なので、`Option<T>: Debug`に // 境界を設定したい。他の方法では間違った境界しか設定できない。 fn print_in_option(self) { println!("{:?}", Some(self)); } } fn main() { let vec = vec![1, 2, 3]; vec.print_in_option(); }
こちらも参照:
New Typeイディオム
newtypeイディオムは正しい型の値が供給されることをコンパイル時に保証します。
例えば、年齢確認プログラムでは、Years型の値が関数に渡される必要があります。
訳注: わかりにくかったので補足すると、年を表す数値も日数を表す数値も
i64ですが、だからといって年齢確認用の関数に日数を渡されても困ります。なので、 新しい型を作って数値の意味を明示する、ということです。
struct Years(i64); struct Days(i64); impl Years { pub fn to_days(&self) -> Days { Days(self.0 * 365) } } impl Days { /// 部分的な年を切り捨てます pub fn to_years(&self) -> Years { Years(self.0 / 365) } } fn old_enough(age: &Years) -> bool { age.0 >= 18 } fn main() { let age = Years(5); let age_days = age.to_days(); println!("Old enough {}", old_enough(&age)); println!("Old enough {}", old_enough(&age_days.to_years())); // println!("Old enough {}", old_enough(&age_days)); }
最後のprint文をアンコメントしても、コンパイルが通りません。
基本型の値をnewtypeとして作ると、このようにタプルのような構文で値が参照できます。
struct Years(i64); fn main() { let years = Years(42); let years_as_primitive: i64 = years.0; }
こちらも参照:
関連要素
「関連要素」とはある規則に従った様々な型の要素の集合のことです。
これはトレイトの拡張であり、トレイト内で要素を定義できます。
そのような要素の一つに関連型があります。これにより、トレイトでジェネリックな
コンテナ型を扱うときに、よりシンプルな書き方ができます。
こちらも参照:
問題
コンテナ型に、その要素に対してジェネリックなトレイトを実装した時、そのトレイトを 使用する人は、すべてのジェネリック型を明記する必要があります。
以下の例では、Containsトレイトはジェネリック型AとBの使用を許可しています。
このトレイトは、その後Container型に対して実装されていますが、その際、後で
fn difference()が使用できるように、AとBがi32であると明記しています。
Containsはジェネリックであるため、明示的にすべてのジェネリック型をfn difference()
にする必要があります。しかし、CによってAとBは決定できるはずです。次の節で紹介する
関連型で、この冗長さを解消できます。
struct Container(i32, i32); // 2つの要素がコンテナの中にあることをチェックするトレイト // 最初と最後の値を取得することもできる。 trait Contains<A, B> { fn contains(&self, _: &A, _: &B) -> bool; // 明示的に`A`と`B`が必要。 fn first(&self) -> i32; // `A`と`B`は不要。 fn last(&self) -> i32; // `A`と`B`は不要。 } impl Contains<i32, i32> for Container { // 保存されている値が等しければtrueを返す fn contains(&self, number_1: &i32, number_2: &i32) -> bool { (&self.0 == number_1) && (&self.1 == number_2) } // 最初の値を返す。 fn first(&self) -> i32 { self.0 } // 最後の値を返す。 fn last(&self) -> i32 { self.1 } } // `C`は`A`を`B`含んでいるため、`A`と`B`の記述は冗長。 fn difference<A, B, C>(container: &C) -> i32 where C: Contains<A, B> { container.last() - container.first() } fn main() { let number_1 = 3; let number_2 = 10; let container = Container(number_1, number_2); println!("Does container contain {} and {}: {}", &number_1, &number_2, container.contains(&number_1, &number_2)); println!("First number: {}", container.first()); println!("Last number: {}", container.last()); println!("The difference is: {}", difference(&container)); }
こちらも参照:
関連型
「関連型」を使えば、コンテナ型の中の要素を出力型として書くことができるため、 可読性が上げられます。トレイトを定義する構文は以下の通りです。
#![allow(unused_variables)] fn main() { // `A`と`B`はトレイト内で`type`キーワードを使って定義できる。 // (注意: この文脈での`type`は型エイリアスを作る`type`とは別物です。) trait Contains { type A; type B; // これらの型をジェネリックに使うため、構文を変える。 fn contains(&self, &Self::A, &Self::B) -> bool; } }
Containsトレイトを使用する時に、AとBを明示することは
もはや必要ありません。
// 関連型を使わない時
fn difference<A, B, C>(container: &C) -> i32 where
C: Contains<A, B> { ... }
// 関連型を使った時
fn difference<C: Contains>(container: &C) -> i32 { ... }
前の節の例を関連型を使って書き直しましょう。
struct Container(i32, i32); // コンテナが2つの要素を持つことをチェックするトレイト // 最初と最後の要素も取得できます。 trait Contains { // メソッドで使われるジェネリック型を定義する type A; type B; fn contains(&self, _: &Self::A, _: &Self::B) -> bool; fn first(&self) -> i32; fn last(&self) -> i32; } impl Contains for Container { // `A`と`B`の型を定義します。もし入力型が`Container(i32, i32)`なら、 // 出力型は`i32`と`i32`になります。 type A = i32; type B = i32; // `&Self::A`と`&Self::B`も利用可能です。 fn contains(&self, number_1: &i32, number_2: &i32) -> bool { (&self.0 == number_1) && (&self.1 == number_2) } // 最初の値を取得する fn first(&self) -> i32 { self.0 } // 最後の値を取得する fn last(&self) -> i32 { self.1 } } fn difference<C: Contains>(container: &C) -> i32 { container.last() - container.first() } fn main() { let number_1 = 3; let number_2 = 10; let container = Container(number_1, number_2); println!("Does container contain {} and {}: {}", &number_1, &number_2, container.contains(&number_1, &number_2)); println!("First number: {}", container.first()); println!("Last number: {}", container.last()); println!("The difference is: {}", difference(&container)); }
幽霊型パラメータ
幽霊型は実行時には存在しませんが、コンパイル時に静的に 型チェックをするような型です。
データ型はマーカーとして余分なジェネリック型パラメータを持ち、それを使って コンパイル時に型チェックすることができます。このパラメータはストレージの 余分なスペースを消費せず、実行時の動作にも影響しません。
以下の例では、幽霊型(std::marker::PhantomData)というマーカーを使って それぞれ異なった型の値を持つタプルを作成します。
use std::marker::PhantomData; // ジェネリックな幽霊型タプル構造体。`A`と幽霊型`B`からなる。 #[derive(PartialEq)] // この型で`==`での比較を可能にする。 struct PhantomTuple<A, B>(A,PhantomData<B>); // 同様に構造体を定義 #[derive(PartialEq)] // Allow equality test for this type. struct PhantomStruct<A, B> { first: A, phantom: PhantomData<B> } // 注意: ストレージはジェネリック型`A`のために確保されますが、`B`に対してはされません。 // なので、`B`を処理に使うことはできません。 fn main() { // ここでは、`f32`と`f64`は幽霊型パラメータです。 // PhantomTupleを`<char, f32>`で定義する。 let _tuple1: PhantomTuple<char, f32> = PhantomTuple('Q', PhantomData); // PhantomTupleを`<char, f64>`で定義する。 let _tuple2: PhantomTuple<char, f64> = PhantomTuple('Q', PhantomData); // `<char, f32>`で定義する let _struct1: PhantomStruct<char, f32> = PhantomStruct { first: 'Q', phantom: PhantomData, }; // `<char, f64>`で定義する。 let _struct2: PhantomStruct<char, f64> = PhantomStruct { first: 'Q', phantom: PhantomData, }; // コンパイルエラー! 型が違うため比較できません //println!("_tuple1 == _tuple2 yields: {}", // _tuple1 == _tuple2); // コンパイルエラー! 型が違うため比較できません //println!("_struct1 == _struct2 yields: {}", // _struct1 == _struct2); }
こちらも参照:
テストケース: 単位の明確化
共通の単位同士を扱う際のチェックのために、Addを幽霊型を用いた実装に
すると便利です。その場合Addトレイトは以下のようになります。
// このように定義しておくと、`Self + RHS(右辺値) = Output`であることが保証され、
// 実装時にRHSを省略すると、デフォルトでSelfと同じになります。
pub trait Add<RHS = Self> {
type Output;
fn add(self, rhs: RHS) -> Self::Output;
}
// `T<U> + T<U> = T<U>`であるため、`Output`は`T<U>`である必要があります。
impl<U> Add for T<U> {
type Output = T<U>;
...
}
以下は全体を示した例です。
use std::ops::Add; use std::marker::PhantomData; /// ユニット型を定義する。 #[derive(Debug, Clone, Copy)] enum Inch {} #[derive(Debug, Clone, Copy)] enum Mm {} /// `Length`は`Unit`型の幽霊型パラメータを持つ型 /// そして長さの型はジェネリックではありません(`f64`です)。 /// /// `f64`ははじめから`Clone`と`Copy`トレイトを実装しています。 #[derive(Debug, Clone, Copy)] struct Length<Unit>(f64, PhantomData<Unit>); /// `Add`トレイトで`+`演算子の振る舞いを定義します。 impl<Unit> Add for Length<Unit> { type Output = Length<Unit>; // add()は合計値を含む新しい`Length`構造体を返します。 fn add(self, rhs: Length<Unit>) -> Length<Unit> { // `+`は`f64`に対する`Add`の実装を呼び出します。 Length(self.0 + rhs.0, PhantomData) } } fn main() { // `one_foot`は幽霊型パラメータ`Inch`を持つ let one_foot: Length<Inch> = Length(12.0, PhantomData); // `one_meter`は幽霊型パラメータ`Mm`を持つ let one_meter: Length<Mm> = Length(1000.0, PhantomData); // `+`は`Length<Unit>`の`add()`メソッドを呼び出します。 // // `Length`は`Copy`を実装しているので、`add()`は // `one_foot`や`one_meter`を消費せず、コピーします。 let two_feet = one_foot + one_foot; let two_meters = one_meter + one_meter; // 問題なく実行されていることの確認 println!("one foot + one_foot = {:?} in", two_feet.0); println!("one meter + one_meter = {:?} mm", two_meters.0); // 違う単位間では計算できない。 // コンパイルエラー: 型が違います。 //let one_feter = one_foot + one_meter; }
こちらも参照:
スコープのルール
スコープは所有権や借用、ライフタイムを考える上で重要な概念です。 スコープで、いつ参照が有効になり、いつリソースが開放され、 そしていつ変数が作られ、破棄されるかを示すことができます。
RAII
Rustの変数はスタック上のデータを保持するだけでなく、リソースを所有します。
例えば、Box<T>はヒープ上のメモリを所有します。RustはRAII(Resource
Acquisition Is Initialization、リソースの取得は初期化である)に従っているため、
オブジェクトがスコープを出ると、デストラクタが呼び出され、所有していたリソースが
開放されます。
この振る舞いによって、手動でメモリを開放しなくて良いため、リソースリークが防げます。 もうメモリリークを気にする必要はないのです! 以下に例があります。
// raii.rs fn create_box() { // ヒープに整数を格納する let _box1 = Box::new(3i32); // `_box1`は破棄され、メモリが開放されます。 } fn main() { // ヒープに整数を格納する let _box2 = Box::new(5i32); // ネストされたスコープ { // ヒープに整数を格納する let _box3 = Box::new(4i32); // `_box3`は破棄され、メモリが開放されます } // 遊びでたくさんメモリを確保してみる // 手動でメモリを開放する必要はありません! for _ in 0u32..1_000 { create_box(); } // `_box2`が破棄され、メモリが開放されます }
もちろん、valgrindでメモリエラーを確認します。
$ rustc raii.rs && valgrind ./raii
==26873== Memcheck, a memory error detector
==26873== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==26873== Using Valgrind-3.9.0 and LibVEX; rerun with -h for copyright info
==26873== Command: ./raii
==26873==
==26873==
==26873== HEAP SUMMARY:
==26873== in use at exit: 0 bytes in 0 blocks
==26873== total heap usage: 1,013 allocs, 1,013 frees, 8,696 bytes allocated
==26873==
==26873== All heap blocks were freed -- no leaks are possible
==26873==
==26873== For counts of detected and suppressed errors, rerun with: -v
==26873== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)
ひとつもリークしていません!
デストラクタ
RustのデストラクタはDropトレイトによって実装されています。デストラクタは
リソースがスコープを抜けた時に呼び出されます。このトレイトは、すべての型に実装
する必要はなく、デストラクタに独自のロジックを実装する必要がある場合のみ実装
します。
どのようにDropトレイトが動くかを見てみるため、下の例を実行してください。
main関数内の変数がスコープを抜けた時、独自のデストラクタが呼び出されます。
struct ToDrop; impl Drop for ToDrop { fn drop(&mut self) { println!("ToDrop is being dropped"); } } fn main() { let x = ToDrop; println!("Made a ToDrop!"); }
こちらも参照:
所有権とムーブ
変数は所有するリソースを開放する必要があるため、 リソースは一つの所有者 しか持ちません。リソースを2度以上開放しないためでもあります。すべての変数 がリソースを持つわけではないことに注意してください(例えば参照)。
変数に値を代入(let x = y)したときや、関数に値を渡した(foo(x))とき、
リソースの所有権が渡されます。これをRustでは、ムーブと呼んでいます。
リソースをムーブしたら、元の所有者はもうそのリソースを使うことができません。 これによって、危険なポインタを作成するのを防ぎます。
// ヒープに確保したメモリの所有権を必要とする関数 fn destroy_box(c: Box<i32>) { println!("Destroying a box that contains {}", c); // `c`は破棄され、メモリが開放される } fn main() { // _スタック_に整数を確保する let x = 5u32; // `x`を`y`に*コピー*する。所有権はムーブされない let y = x; // どちらの値も独立して使える println!("x is {}, and y is {}", x, y); // `a`は_ヒープ_に確保した整数のポインタ let a = Box::new(5i32); println!("a contains: {}", a); // `a`を`b`に*ムーブ*する let b = a; // `a`のポインタアドレスは`b`にコピーされる。 // どちらも同じヒープのポインタをもつが、現在 // `b`が所有している。 // エラー! `a`はヒープメモリのデータを所有していないため、 // データを使用できない。 //println!("a contains: {}", a); // TODO ^ この行をアンコメントしてみてください // `b`が持っているメモリの所有権を取る関数 destroy_box(b); // ここでヒープメモリは開放されたので、開放されたメモリのポインタを // 使うことになるが、これはコンパイラによって禁止されている。 // エラー! 前のエラーと同じ理由 //println!("b contains: {}", b); // TODO ^ この行をアンコメントしてみてください }
可変性
所有権がムーブしたときに、データの可変性も変更できる。
fn main() { let immutable_box = Box::new(5u32); println!("immutable_box contains {}", immutable_box); // 可変性のエラー //*immutable_box = 4; // 所有権(と可変性)を変更してBoxを*ムーブ*する。 let mut mutable_box = immutable_box; println!("mutable_box contains {}", mutable_box); // Boxの要素を変更する *mutable_box = 4; println!("mutable_box now contains {}", mutable_box); }
借用
ほとんどの場合、データの所有権を取らずにデータにアクセスしたいです。このために、
Rustは借用のメカニズムを使用しています。オブジェクトを値(T)で渡す代わりに、
参照(&T)で渡すことができます。
コンパイラは参照が常に有効なオブジェクトのポインタであることを(ボローチェッカー によって)保証します。つまり、オブジェクトの参照が存在する限り、そのオブジェクトは 破棄されません。
// Boxの所有権をとって、破棄する関数 fn eat_box_i32(boxed_i32: Box<i32>) { println!("Destroying box that contains {}", boxed_i32); } // i32を借用する関数 fn borrow_i32(borrowed_i32: &i32) { println!("This int is: {}", borrowed_i32); } fn main() { // ヒープ上のi32とスタック上のi32を作る let boxed_i32 = Box::new(5_i32); let stacked_i32 = 6_i32; // Boxの要素を借用する。所有権が取られないため、 // もう1度借用できる。 borrow_i32(&boxed_i32); borrow_i32(&stacked_i32); { // Box上のデータの参照を作る let _ref_to_i32: &i32 = &boxed_i32; // エラー! // `boxed_i32`は後に借用されるため、破棄できません。 eat_box_i32(boxed_i32); // FIXME ^ この行をコメントアウトしてください // 中の値が破棄された後に`_ref_to_i32`を使用しようとする borrow_i32(_ref_to_i32); // `_ref_to_i32`がスコープを出たため、もう借用されることはない } // 今は`boxed_i32`は`eat_box`に所有権を与えて破棄することができる。 eat_box_i32(boxed_i32); }
可変性
可変なデータは&mut Tで可変的に借用することができます。これ
可変参照と呼ばれ、借用した人に読み書きを許可します。
&Tは不変なデータの参照をとり、借用した人はデータの読み取りは
できても、変更はできません。
#[allow(dead_code)] #[derive(Clone, Copy)] struct Book { // `&'static str`は読み取り専用メモリに確保した文字列の参照 author: &'static str, title: &'static str, year: u32, } // この関数はbookの参照をとる fn borrow_book(book: &Book) { println!("I immutably borrowed {} - {} edition", book.title, book.year); } // この関数はbookのか変参照をとり、`year`を2014に変更する fn new_edition(book: &mut Book) { book.year = 2014; println!("I mutably borrowed {} - {} edition", book.title, book.year); } fn main() { // 不変なBook、`immutabook`を作る let immutabook = Book { // `&'static str`に格納される author: "Douglas Hofstadter", title: "Gödel, Escher, Bach", year: 1979, }; // `immutabook`の可変なコピー、`mutabook`を作る let mut mutabook = immutabook; // 不変なオブジェクトを不変的に借用する borrow_book(&immutabook); // 可変なオブジェクトを不変的に借用する borrow_book(&mutabook); // 可変なオブジェクトを可変的に借用する new_edition(&mut mutabook); // エラー! 不変なオブジェクトは可変参照できません new_edition(&mut immutabook); // FIXME ^ この行をコメントアウトする }
See also:
エイリアシング
データは何度でも不変借用できますが、不変借用されている間はもとのデータは可変借用 できません。言い換えれば、同時に一つの可変参照ができ、もとのデータは可変参照が 最後に使われた後でのみ借用できるということです。
struct Point { x: i32, y: i32, z: i32 } fn main() { let mut point = Point { x: 0, y: 0, z: 0 }; let borrowed_point = &point; let another_borrow = &point; // データには参照または所有者のみアクセスできます。 println!("Point has coordinates: ({}, {}, {})", borrowed_point.x, another_borrow.y, point.z); // エラー! 現在不変参照されているため、`point`を可変参照 // することはできません。 // let mutable_borrow = &mut point; // TODO ^ この行をアンコメントしてみてください // 借用された値はもう一度使用できます。 println!("Point has coordinates: ({}, {}, {})", borrowed_point.x, another_borrow.y, point.z); // もう不変参照は使われないため、残りのコードでは // 可変参照として再び借用できます。 let mutable_borrow = &mut point; // 可変参照でデータを変更する mutable_borrow.x = 5; mutable_borrow.y = 2; mutable_borrow.z = 1; // エラー! `point`は現在可変借用されているため、不変借用はできません。 // let y = &point.y; // TODO ^ この行をアンコメントしてみてください // エラー! `println!`は不変参照を必要とするため、呼び出せません。 // println!("Point Z coordinate is {}", point.z); // TODO ^ この行をアンコメントしてみてください // Ok! 可変参照は不変参照として`println!`に渡せます println!("Point has coordinates: ({}, {}, {})", mutable_borrow.x, mutable_borrow.y, mutable_borrow.z); // 可変参照はもう使われないので、残りのコードでは再度借用できます。 let new_borrowed_point = &point; println!("Point now has coordinates: ({}, {}, {})", new_borrowed_point.x, new_borrowed_point.y, new_borrowed_point.z); }
refパターン
パターンマッチングやlet束縛による分割代入をするとき、構造体やタプルのフィールド
の参照をとるのにrefキーワードが使用できます。下の例では、有用な使い方をいくつか
載せています。
#[derive(Clone, Copy)] struct Point { x: i32, y: i32 } fn main() { let c = 'Q'; // `ref`を使って左辺で借用を表している。 // 右辺で`&`を使って借用するのと等価 let ref ref_c1 = c; let ref_c2 = &c; println!("ref_c1 equals ref_c2: {}", *ref_c1 == *ref_c2); let point = Point { x: 0, y: 0 }; // `ref`は構造体の分割代入にも使える。 let _copy_of_x = { // `ref_to_x`は`point`のフィールド`x`の参照 let Point { x: ref ref_to_x, y: _ } = point; // `point`の`x`フィールドのコピーを返す *ref_to_x }; // `point`の可変コピー let mut mutable_point = point; { // `ref`と`mut`を一緒に使うと可変参照が取れる。 let Point { x: _, y: ref mut mut_ref_to_y } = mutable_point; // `mutable_point`の`y`フィールドを可変参照から変更する。 *mut_ref_to_y = 1; } println!("point is ({}, {})", point.x, point.y); println!("mutable_point is ({}, {})", mutable_point.x, mutable_point.y); // ポインタを含む可変なタプル let mut mutable_tuple = (Box::new(5u32), 3u32); { // `mutable_tuple`を分割代入し、`last`の値を変更する。 let (_, ref mut last) = mutable_tuple; *last = 2u32; } println!("tuple is {:?}", mutable_tuple); }
ライフタイム
ライフタイムはコンパイラ(具体的にはボローチェッカー)が、すべての借用に 問題がないことを確認するために使う仕組みです。具体的には、変数のライフタイムは 作られたときに始まり、破棄されたときに終わります。ライフタイムとスコープはしばしば 同じ扱いをされますが、同じではありません。
例えば、変数を&で借用した場合を考えます。この借用はライフタイムを持ち、
宣言されたときに始まり、貸し手が破棄されるまで続きます。しかし、借用のスコープ
は参照が使われるときに決まります。
以下の例や残りの節で、ライフタイムはスコープとどのように関係し、 どのように異なるのかを見ていきます。
// 以下では、各変数に対するライフタイムを線で表します // `i`は`borrow1`と`borrow2`のスコープを完全に含むため、 // 一番長いです。`borrow1`と`borrow2`のライフタイムは // 重なりません。 fn main() { let i = 3; // `i`のライフタイムが始まる。─────────────────────┐ // │ { // │ let borrow1 = &i; // `borrow1`のライフタイムが始まる。───┐│ // ││ println!("borrow1: {}", borrow1); // ││ } // `borrow1が終わる。──────────────────────────────────────┘│ // │ // │ { // │ let borrow2 = &i; // `borrow2`のライフタイムが始まる。───┐│ // ││ println!("borrow2: {}", borrow2); // ││ } // `borrow2`が終わる。─────────────────────────────────────┘│ // │ } // ライフタイムが終わる。─────────────────────────────────────┘
ライフタイムのラベルにはいかなる名前、型も代入できないことに注意してください。 この制限によって、ライフタイムはうまく使えるようになります。
明示的アノテーション
ボローチェッカーは参照がどれだけの間有効か示すのに、ライフタイムの明示的 アノテーションを使用します。ライフタイムが省略1されない場合、Rustでは 参照のライフタイムがどのようなものかを明示的に示すことが必要です。明示的 アノテーションは、以下のようにアポストロフィの後に文字を置くことで表せます。
foo<'a>
// `foo`はライフタイムパラメータ`'a`を持っています。
closuresのように、ライフタイムを使うにはジェネリクスが必要です。
更に、fooのライフタイムは'aを超えることはないということを表しています。
型を明示した場合は'aは&'a Tとなります。
複数のライフタイムを持つ時、構文は以下のようになります。
foo<'a, 'b>
// `foo`はライフタイムパラメータ`'a`と`'b`を持っています。
この時、fooのライフタイムは'aも'bも超えることはないということを表しています。
以下の例で明示的アノテーションの使い方を見てください。
// `print_refs`は少なくとも`print_refs`のライフタイムより長い2つ // の異なったライフタイムを持つ2つの`i32`の参照をとります。 fn print_refs<'a, 'b>(x: &'a i32, y: &'b i32) { println!("x is {} and y is {}", x, y); } // 引数は取らないが、ライフタイムパラメータ`'a`を持つ関数。 fn failed_borrow<'a>() { let _x = 12; // エラー: `_x`のライフタイムが短いです。 //let y: &'a i32 = &_x; // `'a`を関数内で明示的アノテーションとして使うことを試みましたが、`&_x`の // ライフタイムは`y`より短くいため、できません。短いライフタイムを長い // ライフタイムを持つものに強制させることはできない。 } fn main() { // 下で使う変数を作る。 let (four, nine) = (4, 9); // 両方の変数の借用(`&`)が関数に渡される。 print_refs(&four, &nine); // 借用された入力は借用したものより長生きしなくてはならない。 // 言い換えれば、`four`と`nine`は`print_refs`より長いライフタイムを // 持つ必要がある。 failed_borrow(); // `failed_borrow`関数のライフタイムよりも長くなるような`'a`の参照は // 存在しませんが、このような場合、`'a`は`'static`になるため、長いです。 }
省略 ライフタイムが暗示的に注釈されることを意味します。
こちらも参照:
関数
省略しなかった場合、関数のライフタイムにはいくつかの制限があります。
- すべての参照は明示的なライフタイムを持つ必要がある。
- 返り値となる参照は、入力と同じか、
'staticライフタイムを持つ必要がある。
更に、返り値となる参照は、入力がなけく、無効なデータへの参照だった場合エラーになります。 以下の例ではライフタイムを使ったいくつかの有効な関数を示しています。
// 少なくともこの関数より長いライフタイム`'a`を持つ // 一つの参照を入力として受け取る関数 fn print_one<'a>(x: &'a i32) { println!("`print_one`: x is {}", x); } // ライフタイムが合っても可変参照は使えます。 fn add_one<'a>(x: &'a mut i32) { *x += 1; } // 異なるライフタイムを持つ複数の要素。ここでは、同じライフタイム`'a` // にしても良いかもしれませんが、もっと複雑なケースでは異なるライフタイム // が必要です。 fn print_multi<'a, 'b>(x: &'a i32, y: &'b i32) { println!("`print_multi`: x is {}, y is {}", x, y); } // 渡された変数はそのまま返すことができます。 // しかし、正しいライフタイムが返される必要があります。 fn pass_x<'a, 'b>(x: &'a i32, _: &'b i32) -> &'a i32 { x } //fn invalid_output<'a>() -> &'a String { &String::from("foo") } // 上は無効です。`'a`は関数より長いライフタイムを持つ必要がありますが、 // ここで、`&String::from("foo")`は`String`を作り、その参照を返します。 // スコープを抜けたときにデータがdropされるため、無効なデータへの参照を // 返すことになります。 fn main() { let x = 7; let y = 9; print_one(&x); print_multi(&x, &y); let z = pass_x(&x, &y); print_one(z); let mut t = 3; add_one(&mut t); print_one(&t); }
こちらも参照:
メソッド
メソッドは関数と同じようにアノテーションできます。
struct Owner(i32); impl Owner { // 普通の関数と同じようにライフタイムを明示する fn add_one<'a>(&'a mut self) { self.0 += 1; } fn print<'a>(&'a self) { println!("`print`: {}", self.0); } } fn main() { let mut owner = Owner(18); owner.add_one(); owner.print(); }
こちらも参照:
構造体
構造体も関数と同じようにアノテーションできます。
// `Borrowed`は`i32`の参照を保存します。`i32`の参照は`Borrowed`より // 長生きする必要があります。 #[derive(Debug)] struct Borrowed<'a>(&'a i32); // 同じように、両方共構造体より長生きする必要があります。 #[derive(Debug)] struct NamedBorrowed<'a> { x: &'a i32, y: &'a i32, } // `i32`またはその参照を格納するenum。 #[derive(Debug)] enum Either<'a> { Num(i32), Ref(&'a i32), } fn main() { let x = 18; let y = 15; let single = Borrowed(&x); let double = NamedBorrowed { x: &x, y: &y }; let reference = Either::Ref(&x); let number = Either::Num(y); println!("x is borrowed in {:?}", single); println!("x and y are borrowed in {:?}", double); println!("x is borrowed in {:?}", reference); println!("y is *not* borrowed in {:?}", number); }
こちらも参照:
トレイト
トレイトでのライフタイムも基本的に関数と同じです。
implも明示的アノテーションを持てることに注意してください。
// ライフタイムのアノテーションを持つ構造体 #[derive(Debug)] struct Borrowed<'a> { x: &'a i32, } // implブロックに対してライフタイムを明示する。 impl<'a> Default for Borrowed<'a> { fn default() -> Self { Self { x: &10, } } } fn main() { let b: Borrowed = Default::default(); println!("b is {:?}", b); }
こちらも参照:
ライフタイムの境界
ジェネリックに境界が設定できたように、ライフタイム(これもジェネリックです)
にも境界が使えます。:はここでは違った意味を持ちますが、+は同じ意味です。
以下の注意を読んでください。
T: 'a:Tのすべての参照が'aより長生きする必要がある。T: Trait + 'a: 型TはTraitトレイトを実装している必要があり、Tの すべての参照が'aより長生きする必要がある。
下の例ではwhere節を使って上のことを表現しています。
use std::fmt::Debug; // 境界として設定するトレイト #[derive(Debug)] struct Ref<'a, T: 'a>(&'a T); // `Ref`はジェネリック型`T`への参照を持ち、それは未知のライフタイム`'a` // を持ちます。`T`は`T`のすべての*参照*が`'a`より長生きする必要があり、 // `Ref`のライフタイムは`'a`を超えられません。 // `Debug`トレイトを持つジェネリック型`T`をプリントする関数 fn print<T>(t: T) where T: Debug { println!("`print`: t is {:?}", t); } // `Debug`を実装し、`T`すべての参照が`'a`より長生きするような // ジェネリック型`T`の参照をとります。さらに、`'a`は関数より // 長生きする必要があります。 fn print_ref<'a, T>(t: &'a T) where T: Debug + 'a { println!("`print_ref`: t is {:?}", t); } fn main() { let x = 7; let ref_x = Ref(&x); print_ref(&ref_x); print(ref_x); }
こちらも参照:
ライフタイムの圧縮
自分より短いライフタイムを持つものに自分を圧縮し、そのままでは 動かないスコープの中でも使えるようにすることができます。これには、 コンパイラが推論して圧縮する場合と、ライフタイムの違いを見て圧縮する 場合があります。
// ここで、Rustはできるだけ短いライフタイムを推論し、 // 2つの参照をそれに圧縮します。 fn multiply<'a>(first: &'a i32, second: &'a i32) -> i32 { first * second } // `<'a: 'b, 'b>`は、`'a`は少なくとも`'b`より長いことを意味します。 // ここでは、`&'a i32`を`&'b i32`に圧縮して返す。 fn choose_first<'a: 'b, 'b>(first: &'a i32, _: &'b i32) -> &'b i32 { first } fn main() { let first = 2; // 長いライフタイム { let second = 3; // 短いライフタイム println!("The product is {}", multiply(&first, &second)); println!("{} is the first", choose_first(&first, &second)); }; }
静的ライフタイム
Rustはいくつかの定義済みのライフタイムを持ちます。その中の一つが
'staticです。このような2つの状況に出会うかもしれません。
// 'staticライフタイムで参照する let s: &'static str = "hello world"; // トレイト境界としての'static fn generic<T>(x: T) where T: 'static {}
両者は関係していますが、微妙に異なります。Rustを学ぶ際に混同してしまうもの の一つです。それぞれについて例を見ていきましょう。
参照のライフタイム
参照のライフタイムとしての'staticは、プログラムが走っている間中有効なデータ
への参照を表します。これを短いライフタイムに圧縮することもできます。
'staticライフタイムの変数を作る方法は2つありますが、両方とも
バイナリの読み取り専用メモリに保存されます。
static宣言で定数を作る。&'static str型の文字列リテラルを作る。
以下の例はそれぞれの方法を示しています。
// `'static`ライフタイムの定数を作る。 static NUM: i32 = 18; // `NUM`の参照を`'static`ライフタイムを入力のライフタイムに圧縮して // 返す。 fn coerce_static<'a>(_: &'a i32) -> &'a i32 { &NUM } fn main() { { // `string`リテラルを作ってプリントする。 let static_string = "I'm in read-only memory"; println!("static_string: {}", static_string); // `static_string`がスコープを出た時、参照は使えなくなりますが、 // データはバイナリに残ります。 } { // `coerce_static`に使う整数を作る。 let lifetime_num = 9; // `NUM`を`lifetime_num`のライフタイムに圧縮する let coerced_static = coerce_static(&lifetime_num); println!("coerced_static: {}", coerced_static); } println!("NUM: {} stays accessible!", NUM); // NUM: {}はまだアクセスできます。 }
トレイトの境界
トレイトの境界として使った時、静的でない参照を含むことができないことを 意味します。つまり、受け取り手はその型の変数を好きなだけ保持することができ、 dropするまで無効にならないということです。
所有しているすべてのデータは'staicライフタイム境界を持ちますが、その参照は
持ちません。
use std::fmt::Debug; fn print_it( input: impl Debug + 'static ) { println!( "'static value passed in is: {:?}", input ); } fn use_it() { // iは所有していて、参照を持たないので'staticです。 let i = 5; print_it(i); // &iはuse_it()で定義されたスコープでしか使えないので、 // 'staticではありません。 print_it(&i); }
コンパイラはこのように出力します。
error[E0597]: `i` does not live long enough
--> src/lib.rs:15:15
|
15 | print_it(&i);
| ---------^^--
| | |
| | borrowed value does not live long enough
| argument requires that `i` is borrowed for `'static`
16 | }
| - `i` dropped here while still borrowed
こちらも参照:
省略
圧倒的によく使われるライフタイムパターンについては、可読性のために省略 することができます。省略は、単にそれが一般的であるため、存在しています。
次のコードではいくつかの省略の例を紹介します。もっと厳密な記述については、 the bookのlifetime elisionを参照してください。
// `elided_input`と`annotated_input`は同じ意味で、`elided_input`の // ライフタイムはコンパイラによって推論されています。 fn elided_input(x: &i32) { println!("`elided_input`: {}", x); } fn annotated_input<'a>(x: &'a i32) { println!("`annotated_input`: {}", x); } // 同様に、`elided_pass`と`annotated_pass`は同じで、 // `elided_pass`では暗示的に記述が加えられているだけです。 fn elided_pass(x: &i32) -> &i32 { x } fn annotated_pass<'a>(x: &'a i32) -> &'a i32 { x } fn main() { let x = 3; elided_input(&x); annotated_input(&x); println!("`elided_pass`: {}", elided_pass(&x)); println!("`annotated_pass`: {}", annotated_pass(&x)); }
こちらも参照:
トレイト
トレイトは、未知の型Selfに対して実装されたメソッドの集合です。
ここでは、同じトレイトで実装されたメソッドにアクセスすることができます。
トレイトはいかなるデータ型にも実装できます。下の例では、メソッド群Animal
を定義し、Animalトレイトをデータ型Sheepに対して実装することで、Animal
のメソッドをSheepから使えるようにしています。
struct Sheep { naked: bool, name: &'static str } trait Animal { // コンストラクタとなる静的メソッドを定義する。`Self`は実装する型を表す。 fn new(name: &'static str) -> Self; // インスタンスメソッドの定義。ここでは文字列を返す。 fn name(&self) -> &'static str; fn noise(&self) -> &'static str; // トレイトはデフォルト実装を提供することができる。 fn talk(&self) { println!("{} says {}", self.name(), self.noise()); } } impl Sheep { fn is_naked(&self) -> bool { self.naked } fn shear(&mut self) { if self.is_naked() { // 実装者のメソッドは実装者のトレイトで定義したメソッドを使うことができる。 println!("{} is already naked...", self.name()); } else { println!("{} gets a haircut!", self.name); self.naked = true; } } } // `Animal`トレイトを`Sheep`に実装する。 impl Animal for Sheep { // `Self`は実装する型(`Sheep`) fn new(name: &'static str) -> Sheep { Sheep { name: name, naked: false } } fn name(&self) -> &'static str { self.name } fn noise(&self) -> &'static str { if self.is_naked() { "baaaaah?" } else { "baaaaah!" } } // デフォルトメソッドは上書きできる。 fn talk(&self) { // 例えば、熟考を加えることができる。 println!("{} pauses briefly... {}", self.name, self.noise()); // {}は一息おいた... {} } } fn main() { // この場合、型注釈は必要です。 let mut dolly: Sheep = Animal::new("Dolly"); // TODO ^ 型注釈を消してみてください。 dolly.talk(); dolly.shear(); dolly.talk(); }
継承
コンパイラは、#[derive]属性によって、いくつかのトレイトの
標準実装を提供しています。これらのトレイトは、もっと複雑な振る舞いが必要な
場合、手動で実装することもできます。
以下が継承できるトレイトの一覧です。
- 比較トレイト:
Eq、PartialEq、Ord、PartialOrd。 &TをコピーしてTを作成するClone- 'ムーブを行わず、コピーするための
Copy &Tからハッシュを算出するHash- データ型のからのインスタンスを作る
Default {:?}フォーマットで値を出力するDebug
// `Centimeters`は比較できるタプル構造体です。 #[derive(PartialEq, PartialOrd)] struct Centimeters(f64); // `Inches`は出力できるタプル構造体です。 #[derive(Debug)] struct Inches(i32); impl Inches { fn to_centimeters(&self) -> Centimeters { let &Inches(inches) = self; Centimeters(inches as f64 * 2.54) } } // `Seconds`は何も属性がないタプル構造体です。 struct Seconds(i32); fn main() { let _one_second = Seconds(1); // エラー: `Seconds`は`Debug`トレイトを実装していないためプリントできません。 //println!("One second looks like: {:?}", _one_second); // TODO ^ この行をアンコメントしてみてください // エラー: `Seconds`は`PartialEq`トレイトを実装していないため比較できません。 //let _this_is_true = (_one_second == _one_second); // TODO ^ この行をアンコメントしてみてください let foot = Inches(12); println!("One foot equals {:?}", foot); let meter = Centimeters(100.0); let cmp = if foot.to_centimeters() < meter { "smaller" } else { "bigger" }; println!("One foot is {} than one meter.", cmp); }
こちらも参照:
dynでトレイトを返す
Rustコンパイラは返り値にどのくらいスペースが必要か事前に把握する必要があります。これは、すべての関数が具象型を返す必要があることを意味します。他の言語と違って、
異なる実装は異なるメモリ容量を持つため、Animalのようなトレイトを持っていたとしても、Animalを返す関数は書けません。
しかし、これには簡単な回避策があります。トレイとオブジェクトを直接返す代わりに、Animalを含むBox型を返せばよいのです。boxはヒープメモリの参照に
過ぎませんが、参照は固定されたサイズを持っている上、コンパイラはヒープに格納されたAnimalの参照を返すことが保証されているため、関数からトレイトを返すことが
できます!
Rustはヒープへのメモリ確保ができるだけ明示的になるようにしてきました。そのため、ヒープに確保されたトレイトの参照を返すときも、
返す型をdynキーワードで明示しなければいけません (例えばBox<dyn Animal>)
struct Sheep {} struct Cow {} trait Animal { // インスタンスメソッド fn noise(&self) -> &'static str; } // `Sheep`に`Animal`トレイトを実装する。 impl Animal for Sheep { fn noise(&self) -> &'static str { "baaaaah!" } } // `Cow`に`Animal`トレイトを実装する。 impl Animal for Cow { fn noise(&self) -> &'static str { "moooooo!" } } // Animalを実装したいくつかの構造体を返しますが、コンパイル時にはその型がわかりません。 fn random_animal(random_number: f64) -> Box<dyn Animal> { if random_number < 0.5 { Box::new(Sheep {}) } else { Box::new(Cow {}) } } fn main() { let random_number = 0.234; let animal = random_animal(random_number); println!("You've randomly chosen an animal, and it says {}", animal.noise()); }
演算子オーバーロード
Rustでは、ほとんどの演算子がトレイトによってオーバーロードできます。つまり、いくつかの演算子は
引数によって違う操作を行うことができます。これは、演算子がメソッド呼び出しの糖衣構文だからこそ
実現できます。例えば、a + bの+演算子はaddメソッドをa.add(b)のように呼び出します。
このaddメソッドはAddトレイトによって定義されていないため、+演算子はAddトレイトを
実装すれば使えるようになります。
Addのような、演算子オーバーロードに使うトレイトの一覧はcore::opsにあります。
use std::ops; struct Foo; struct Bar; #[derive(Debug)] struct FooBar; #[derive(Debug)] struct BarFoo; // `std::ops::Add`トレイトは`+`演算子の動作を定義するのに使います。 // ここでは、右辺値が`Bar`のときの足し算を定義する`Add<Bar>`を実装します。 // このブロックはFoo + Bar = FooBarを実装しています。 impl ops::Add<Bar> for Foo { type Output = FooBar; fn add(self, _rhs: Bar) -> FooBar { println!("> Foo.add(Bar) was called"); FooBar } } // 型を逆にすることで、交換法則に従わない実装を作ることができます。 // ここでは、右辺値が`Foo`のときの足し算を定義する`Add<Foo>`を実装します。 // このブロックはBar + Foo = BarFooを定義しています。 impl ops::Add<Foo> for Bar { type Output = BarFoo; fn add(self, _rhs: Foo) -> BarFoo { println!("> Bar.add(Foo) was called"); BarFoo } } fn main() { println!("Foo + Bar = {:?}", Foo + Bar); println!("Bar + Foo = {:?}", Bar + Foo); }
こちらも参照
Drop
Dropトレイトは唯一つのメソッドdropを持ち、オブジェクトがスコープを抜けたときに
自動的に呼び出されます。Dropトレイトは主に実装者のインスタンスを開放するのに使われます。
Box、Vec、String、File、Process などはリソースを開放するために
Dropトレイトを実装している型の例です。Dropはカスタム型に手動で実装することもできます。
以下の例では、dropが呼び出されたときにコンソールに出力します。
struct Droppable { name: &'static str, } // `drop`が呼び出されたときにコンソールに出力する小さな実装 impl Drop for Droppable { fn drop(&mut self) { println!("> Dropping {}", self.name); } } fn main() { let _a = Droppable { name: "a" }; // ブロックA { let _b = Droppable { name: "b" }; // ブロックB { let _c = Droppable { name: "c" }; let _d = Droppable { name: "d" }; println!("Exiting block B"); } println!("Just exited block B"); println!("Exiting block A"); } println!("Just exited block A"); // `drop`関数を使って手動で呼び出すこともできます。 drop(_a); // TODO ^ この行をコメントアウトしてください。 println!("end of the main function"); // `_a`はもう手動で`drop`されたので、ここでは`drop`されません。 }
イテレータ
Iteratorトレイトは配列のようなコレクションにイテレータを実装するトレイトです。
このトレイトは次の要素を表すnextメソッドのみ実装すればよく、これはimplブロックで
実装するか、(配列やrangeのように)もう実装されていることもあります。
一般的なシチュエーションでの利便性のため、forは自動的にコレクションに対して
.into_iter()メソッドを使い、イテレータを作ります。
struct Fibonacci { curr: u32, next: u32, } // `Fibonacci`に`Iterator`を実装する。 // `Iterator`トレイトは、次の要素を返す`next`メソッドのみ実装されていれば良い。 impl Iterator for Fibonacci { type Item = u32; // ここで、`.curr`と`.next`を使って数列を作り、 // `Option<T>`型を返します。 // * `Iterator`が終われば、`None`を返します。 // * そうでなければ、次の値を`Some`でくるみ、返却します。 fn next(&mut self) -> Option<u32> { let new_next = self.curr + self.next; self.curr = self.next; self.next = new_next; // フィボナッチ数列には終わりは無いため、`Iterator`は`None`を // 返さず、いつも`Some`を返します。 Some(self.curr) } } // フィボナッチ数列生成器を返す。 fn fibonacci() -> Fibonacci { Fibonacci { curr: 0, next: 1 } } fn main() { // `0..3`は0、1、2からなるイテレータを生成する let mut sequence = 0..3; println!("Four consecutive `next` calls on 0..3"); // 0..3から4回連続で`next`を呼ぶ。 println!("> {:?}", sequence.next()); println!("> {:?}", sequence.next()); println!("> {:?}", sequence.next()); println!("> {:?}", sequence.next()); // `for`はイテレータを`None`が出るまで続ける。 // それぞれの`Some`は変数(ここでは`i`)に分割代入される。 println!("Iterate through 0..3 using `for`"); // `for`を使って0..3に対して繰り返し処理をする。 for i in 0..3 { println!("> {}", i); } // `take(n)`メソッドは最初`n`回分のイテレータを返す println!("The first four terms of the Fibonacci sequence are: "); // フィボナッチ数列の最初の4つは: for i in fibonacci().take(4) { println!("> {}", i); } // `skip(n)`メソッドは最初の`n`回をなくしたイテレータを返す。 println!("The next four terms of the Fibonacci sequence are: "); // フィボナッチ数列の次の4つは: for i in fibonacci().skip(4).take(4) { println!("> {}", i); } let array = [1u32, 3, 3, 7]; // `iter`メソッドで配列、スライスからイテレータを生成する。 println!("Iterate the following array {:?}", &array); // 以下の配列{:?}をイテレートする for i in array.iter() { println!("> {}", i); } }
impl Trait
もし関数がMyTraitを実装した型を返す時、返り値を-> impl MyTraitと書くことができます。
これによって型指定師をシンプルに書くことができます!
use std::iter; use std::vec::IntoIter; // この関数は2つの`Vec<i32>`を連結し、イテレータを返します。 // 返り値が複雑になっています。 fn combine_vecs_explicit_return_type( v: Vec<i32>, u: Vec<i32>, ) -> iter::Cycle<iter::Chain<IntoIter<i32>, IntoIter<i32>>> { v.into_iter().chain(u.into_iter()).cycle() } // 上と同じですが、返り値に`impl Trait`を使っています。 // とてもシンプルになっています。 fn combine_vecs( v: Vec<i32>, u: Vec<i32>, ) -> impl Iterator<Item=i32> { v.into_iter().chain(u.into_iter()).cycle() } fn main() { let v1 = vec![1, 2, 3]; let v2 = vec![4, 5]; let mut v3 = combine_vecs(v1, v2); assert_eq!(Some(1), v3.next()); assert_eq!(Some(2), v3.next()); assert_eq!(Some(3), v3.next()); assert_eq!(Some(4), v3.next()); assert_eq!(Some(5), v3.next()); println!("all done"); }
さらに、Rustのいくつかの型は直接記述できません。例えば、クロージャは、無名な
具象型を持っていて、impl Trait構文を使わなければ、ヒープにクロージャを格納して
返す必要がありました。しかし、今は静的にこう書けます。
// 入力に`y`を加えて返すクロージャをかえす関数 fn make_adder_function(y: i32) -> impl Fn(i32) -> i32 { let closure = move |x: i32| { x + y }; closure } fn main() { let plus_one = make_adder_function(1); assert_eq!(plus_one(2), 3); }
また、impl Traitはmap、filterなどのクロージャを使ったイテレータを返すのにも
使えます! これによって、mapやfilterを簡単に使えるようになります。以前はクロージャ
の型は名前を持たないため、クロージャを使ったイテレータは、明示的に返り値の型が書けなかった
ものが、impl Traitを使って簡単に書けるようになったからです。
fn double_positives<'a>(numbers: &'a Vec<i32>) -> impl Iterator<Item = i32> + 'a { numbers .iter() .filter(|x| x > &&0) .map(|x| x * 2) }
複製
リソースについて対処する時、デフォルトで代入元や関数呼び出しによって 値が受け渡されます。しかし、ときどきレソースのコピーが作りたい場合が あります。
Cloneトレイトによって、これを正確に行なえます。一般的なもので
いうと、.clone()めそっどはCloneトレイトによって定義されています。
// リソースのない構造体 #[derive(Debug, Clone, Copy)] struct Unit; // `Clone`トレイトを持つタプル構造体 #[derive(Clone, Debug)] struct Pair(Box<i32>, Box<i32>); fn main() { // `Unit`をインスタンス化する let unit = Unit; // `Unit`をコピーする。ムーブするリソースが何もない。 let copied_unit = unit; // `Unit`は独立して使える。 println!("original: {:?}", unit); println!("copy: {:?}", copied_unit); // `Pair`をインスタンス化する。 let pair = Pair(Box::new(1), Box::new(2)); println!("original: {:?}", pair); // `pair`を`moved_pair`にコピーした時、リソースがムーブする。 let moved_pair = pair; println!("copy: {:?}", moved_pair); // エラー! `pair`はリソースを持っていません。 //println!("original: {:?}", pair); // TODO ^ この行をアンコメントしてみてください // `moved_pair`から`cloned_pair`に複製する(リソースも) let cloned_pair = moved_pair.clone(); // std::mem::dropでもとのリソースをdropする。 drop(moved_pair); // エラー! `moved_pair`はdropされています。 //println!("copy: {:?}", moved_pair); // TODO ^ この行をアンコメントしてみてください // .clone()の結果はまだ使えます! println!("clone: {:?}", cloned_pair); }
親トレイト
Rustは「継承(inheritance、deriveとは別)」を持っていませんが、トレイトを他のトレイトの 親として設定することができます。例えば
trait Person { fn name(&self) -> String; } // StudentはPersonを親とする。 // Studentを実装するにはimpl Personが必要。 trait Student: Person { fn university(&self) -> String; } trait Programmer { fn fav_language(&self) -> String; } // CompSciStudent (コンピュータ科学の学生)はProgrammerとStudent両方を親とする。 // CompSciStudentの実装には両方の実装が必要。 trait CompSciStudent: Programmer + Student { fn git_username(&self) -> String; } fn comp_sci_student_greeting(student: &dyn CompSciStudent) -> String { format!( "My name is {} and I attend {}. My Git username is {}", student.name(), student.university(), student.git_username() ) } fn main() {}
こちらも参照:
The Rust Programming Languageのsupertrait節の章
重複するトレイトの明確化
一つの型には多くのトレイトが実装できます。2つのトレイトが同じ名前を持つ場合はどうでしょう? 例えば、多くのトレイトがget()メソッドを持っているかもしれません。
そして、それらは違う型を返すかもしれません!
それぞれのトレイトが独自のimplブロックを使って宣言するため、
どのトレイトのgetメソッドなのか明確にわかります。
それを_呼び出す_ ときはどうでしょうか? それらを明確化するために、 完全修飾構文(Fully Qualified Syntax)を使います。
trait UsernameWidget { // そのウィジェットで指定されたユーザー名を返す fn get(&self) -> String; } trait AgeWidget { // そのウィジェットで指定された年齢を返す fn get(&self) -> u8; } // UsernameWidgetとAgeWidgetを両方実装したフォーム struct Form { username: String, age: u8, } impl UsernameWidget for Form { fn get(&self) -> String { self.username.clone() } } impl AgeWidget for Form { fn get(&self) -> u8 { self.age } } fn main() { let form = Form{ username: "rustacean".to_owned(), age: 28, }; // これをアンコメントすると、「複数の`get`が見つかりました」というエラー // を返します。なぜなら、`get`という名前のメソッドが複数あるためです。 // println!("{}", form.get()); let username = <Form as UsernameWidget>::get(&form); assert_eq!("rustacean".to_owned(), username); let age = <Form as AgeWidget>::get(&form); assert_eq!(28, age); }
こちらも参照:
The Rust Programming Languageの完全修飾構文の節
macro_rules!
Rustはメタプログラミングのための強力なマクロシステムを備えています。前の章で
見たように、マクロは関数のように働き、その名前は!で終わります。しかし、関数
と違って、マクロはコンパイルされる前に、ソースコードに展開されます。また、Cの
ような言語のマクロとは違い、Rustのマクロは抽象構文木に展開されるため、前処理
より強力です。そのため、予期しない優先順位によるバグが起こることはありません。
マクロはmacro_rules!マクロで作ることができます。
// シンプルなマクロ`say_hello` macro_rules! say_hello { // `()`はマクロが引数を取らないことを表します。 () => { // マクロはこのブロックの要素に展開されます。 println!("Hello!"); }; } fn main() { // This call will expand into `println!("Hello");` say_hello!() }
マクロはなぜ有用なのでしょうか?
-
同じことを繰り返さない(Don't repeat yourself, DRY)。似たような処理を複数の場所に 使いたいが、違う型であるためにまとめるのが難しいときがあります。マクロを使えば、 コードの繰り返しを避けられることがあります。(詳しくは後で)
-
ドメイン固有言語。マクロで特有の目的のための特殊な構文が定義できます。(詳しくは 後で)
-
可変長引数。時々可変長の引数を取るインターフェースを書きたいときがあります。例えば
println!はフォーマット文字列に従って様々な数の引数をとります。(詳しくは後で)
構文
以下のサブセクションで、Rustでのマクロの書き方を紹介します。 基本的に3つの概念があります。
マクロ指定子
マクロの引数は$で始まり、そのタイプは指定子で注釈します。
macro_rules! create_function { // このマクロは`ident`指定子の引数をとり、 // `$func_name`という名前の関数を作ります。 // The `ident` designator is used for variable/function names. ($func_name:ident) => { fn $func_name() { // `stringify!`マクロは`ident`を文字列に変換します。 println!("You called {:?}()", stringify!($func_name)); } }; } // 上のマクロを使って、`foo`と`bar`という関数を定義します。 create_function!(foo); create_function!(bar); macro_rules! print_result { // このマクロは式`$expression`をとり、それを文字列にしたものと // その結果を返します。`expr`指定子は式に対して指定します。 ($expression:expr) => { // `stringify!`は式を*そのまま*文字列に変換します。 println!("{:?} = {:?}", stringify!($expression), $expression); }; } fn main() { foo(); bar(); print_result!(1u32 + 1); // ブロックも式です! print_result!({ let x = 1u32; x * x + 2 * x - 1 }); }
これらが利用可能な指定子の一例です。
blockexprは式に使います。identは変数/関数名に使います。itemliteralはリテラルに使いますpat(パターン)pathstmt(式)tt(トークン木)ty(型)vis(可視性指定子(pubなど))
完全な一覧はRustリファレンスを参照してください。
オーバーロード
マクロは違う引数を取るようにオーバーロードできます。
ついては、macro_rules!はmatchブロックのように使用できます。
// `test!` will compare `$left` and `$right` // in different ways depending on how you invoke it: macro_rules! test { // 引数をコンマで区切る必要はありません。 // テンプレートが使えます! ($left:expr; and $right:expr) => { println!("{:?} and {:?} is {:?}", stringify!($left), stringify!($right), $left && $right) }; // ^ それぞれのアームはセミコロンで終わる必要があります。 ($left:expr; or $right:expr) => { println!("{:?} or {:?} is {:?}", stringify!($left), stringify!($right), $left || $right) }; } fn main() { test!(1i32 + 1 == 2i32; and 2i32 * 2 == 4i32); test!(true; or false); }
繰り返し
マクロは引数リストに+を指定することで1回以上の、*を指定することで
0回以上の繰り返しを表現できます。
以下の例で、指定子を$(...),+で囲むことで、コンマで分けられた
一つ以上の式を表現できます。また、最後のセミコロンは省略できます。
// `find_min!`は引数の内最小のものを返します。 macro_rules! find_min { // 基本ケース: ($x:expr) => ($x); // `$x`と1つ以上の`$y,`からなる。 ($x:expr, $($y:expr),+) => ( // `find_min!`を末尾の`$y`に対して実行する std::cmp::min($x, find_min!($($y),+)) ) } fn main() { println!("{}", find_min!(1u32)); println!("{}", find_min!(1u32 + 2, 2u32)); println!("{}", find_min!(5u32, 2u32 * 3, 4u32)); }
同じことを繰り返さない(DRY)
マクロに関数やテストの共通部分を記述することで、DRYコードが実現できます。
これは+=、*=、-=演算子をVec<T>に実装する例です。
use std::ops::{Add, Mul, Sub}; macro_rules! assert_equal_len { // `tt`(token tree)指定子は // 演算子やトークンに使われます。 ($a:expr, $b:expr, $func:ident, $op:tt) => { assert!($a.len() == $b.len(), "{:?}: dimension mismatch: {:?} {:?} {:?}", stringify!($func), ($a.len(),), stringify!($op), ($b.len(),)); }; } macro_rules! op { ($func:ident, $bound:ident, $op:tt, $method:ident) => { fn $func<T: $bound<T, Output=T> + Copy>(xs: &mut Vec<T>, ys: &Vec<T>) { assert_equal_len!(xs, ys, $func, $op); for (x, y) in xs.iter_mut().zip(ys.iter()) { *x = $bound::$method(*x, *y); // *x = x.$method(*y); } } }; } // `add_assign`、`mul_assign`、`sub_assign`関数を実装する。 op!(add_assign, Add, +=, add); op!(mul_assign, Mul, *=, mul); op!(sub_assign, Sub, -=, sub); mod test { use std::iter; macro_rules! test { ($func:ident, $x:expr, $y:expr, $z:expr) => { #[test] fn $func() { for size in 0usize..10 { let mut x: Vec<_> = iter::repeat($x).take(size).collect(); let y: Vec<_> = iter::repeat($y).take(size).collect(); let z: Vec<_> = iter::repeat($z).take(size).collect(); super::$func(&mut x, &y); assert_eq!(x, z); } } }; } // `add_assign`、`mul_assign`、`sub_assign`をテストする。 test!(add_assign, 1u32, 2u32, 3u32); test!(mul_assign, 2u32, 3u32, 6u32); test!(sub_assign, 3u32, 2u32, 1u32); }
$ rustc --test dry.rs && ./dry
running 3 tests
test test::mul_assign ... ok
test test::add_assign ... ok
test test::sub_assign ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured
ドメイン固有言語(DSL)
DSLはRustのマクロによって組み込まれた小さな「言語」です。これは、マクロシステムは普通のRust構文 へ展開しますが、これが小さな言語のように見えることによって成り立っています。これによって、特定の 用途において簡潔で直感的な構文を定義できます(適度に)。
ここで、小さな電卓APIを作ろうと思います。これは式を実行して、 その結果をコンソールに出力するものです。
macro_rules! calculate { (eval $e:expr) => {{ { let val: usize = $e; // 強制的に型を整数にする。 println!("{} = {}", stringify!{$e}, val); } }}; } fn main() { calculate! { eval 1 + 2 // `eval`はRustのキーワードではありません! } calculate! { eval (1 + 2) * (3 / 4) } }
出力:
1 + 2 = 3
(1 + 2) * (3 / 4) = 0
これはとてもシンプルな例ですが、lazy_staticや
clapのようなもっと複雑な例もあります。
さらに、マクロ内に2重の波括弧があることに注意してください。外側のものは
()や[]のような、macro_rules!の構文です。
可変長引数
可変長引数は様々な数の引数をとります。例えば、println!は、フォーマット文字列
に従って、いろいろな数の引数をとります。
前の章のcalculate!マクロを拡張してみましょう。
macro_rules! calculate { // `eval`がひとつだけの場合 (eval $e:expr) => {{ { let val: usize = $e; // Force types to be integers println!("{} = {}", stringify!{$e}, val); } }}; // `eval`が複数ある場合、再帰を使う。 (eval $e:expr, $(eval $es:expr),+) => {{ calculate! { eval $e } calculate! { $(eval $es),+ } }}; } fn main() { calculate! { // 可変長の`calculate!`です! eval 1 + 2, eval 3 + 4, eval (2 * 3) + 1 } }
出力:
1 + 2 = 3
3 + 4 = 7
(2 * 3) + 1 = 7
エラー処理
エラー処理は操作の失敗を処理するのに使われます。例えば、ファイルを読み込む のに失敗したとき、間違った入力で操作を続けるのは問題があります。明示的に このようなエラーを管理することで、残りのプログラムで落とし穴にはまるのを防ぐ ことができます。
次の節から紹介するように、Rustでは様々な方法でこれを対処することができ、その微妙な 違いを異なる場面で使い分けることができます。以下に大雑把な説明を載せます。
明示的なpanicは、主にテストや修復不可能なエラーを処理するのに使います。これは
プロトタイピングにも使えます。例えば、まだ実装していない関数の使用を処理するときは、
unimplementedを使うのが説明的で良いでしょう。テストでは、panicは明示的に失敗する
のに便利です。
Option型は、値がない場合もあり、それはエラーではないという時に使えます。例えば、
/やC:は親ディレクトリを持ちません。Optionを使うとき、値があることが確かに保証
されている場合や、プロトタイピングの用途ではunwrapが有用ですが、なにかの失敗があった
時にエラーメッセージを表示するexpectも有用です。
処理が失敗する可能性があり、それを呼び出し元が処理する必要がある時、Resultを使います。
これについてもunwrapやexpectが使用できます。(テストやプロトタイプ以外では使用しない
でください。)
もっと厳密な議論については、The bookのエラー処理の章を参照してください。
panic
最もシンプルなエラー処理はpanicです。これはエラーメッセージを表示し、
スタックを巻き戻し、通常はプログラムを終了します。
ここでは、エラーしたときにpanicを直接呼び出しています。
fn give_princess(gift: &str) { // プリンセスはヘビが嫌いです。なので、彼女が拒否したときに // 止めてあげる必要があります! if gift == "snake" { panic!("AAAaaaaa!!!!"); } println!("I love {}s!!!!!", gift); } fn main() { give_princess("teddy bear"); give_princess("snake"); }
Optionとunwrap
前の例で、どのようにしてプログラムが失敗したときにストップするかを見て
きました。ここではプリンセスが不適切な贈り物(ヘビ)を受け取ったときに
panicを起こしました。しかし、プリンセスが贈り物をもらえなかったときは
どうでしょうか? このケースも良くないため、処理しなければいけません。
Rustでは使えない、空データを指すポインタの代わりに、なにも贈らないことを
空文字列("")を用いて表すこともできます。
しかし、stdライブラリのOption<T>というenumを使えば、何も無いことを
表すことができます。
Some(T):T型の値がある場合None: なにも値がない場合
これは、matchで明示的に処理することも、unwrapで暗示的に処理することが
できます。暗示的な処理では、その中の値を返し、なければpanicを起こします。
expectを使えば、panicの表示をカスタマイズできますが、unwrapは
意味のあまりない出力しか返しません。以下の例では、明示的に処理し、panic
よりカスタマイズされた結果を返しています。
// 平民は、贈り物をすべて確認して、うまく処理します。 // すべての贈り物は`match`で明示的に処理されています。 fn give_commoner(gift: Option<&str>) { // Specify a course of action for each case. match gift { Some("snake") => println!("Yuck! I'm putting this snake back in the forest."), // オエーッ! このヘビを森に返してくるわ。 Some(inner) => println!("{}? How nice.", inner), // {}? いいじゃないの。 None => println!("No gift? Oh well."), // 何もない? 仕方ないわ。 } } // 私達のプリンセスは世の中を知らないため、ヘビを見ると`panic`してしまいます。 // すべての贈り物は`unwrap`で暗示的に処理されます。 fn give_princess(gift: Option<&str>) { // `unwrap`は`None`を受け取ると`panic`します。 let inside = gift.unwrap(); if inside == "snake" { panic!("AAAaaaaa!!!!"); } println!("I love {}s!!!!!", inside); } fn main() { let food = Some("cabbage"); let snake = Some("snake"); let void = None; give_commoner(food); give_commoner(snake); give_commoner(void); let bird = Some("robin"); let nothing = None; give_princess(bird); give_princess(nothing); }
?でOptionを解析する
match文でOptionを解析できますが、しばしば?演算子を使った方が簡単です。
もしxがOptionだとしたら、x?と書けば、xがSomeならその値を、そうで
なければ現在の関数をNoneを返して終了します。
fn next_birthday(current_age: Option<u8>) -> Option<String> { // `current_age`が`None`なら`None`を返す。 // `current_age`が`Some`なら、`u8`は`next_age`に代入される let next_age: u8 = current_age?; Some(format!("Next year I will be {}", next_age)) }
コードの可読性をあげるため、?はたくさん連結できます。
struct Person { job: Option<Job>, } #[derive(Clone, Copy)] struct Job { phone_number: Option<PhoneNumber>, } #[derive(Clone, Copy)] struct PhoneNumber { area_code: Option<u8>, number: u32, } impl Person { // その人の仕事用の電話番号のエリアコードを取得します。 fn work_phone_area_code(&self) -> Option<u8> { // `?`演算子がなければ、`match`文をたくさんネストする必要がありました。 // 簡単に連結を増やすこともできます。試してみてください。 self.job?.phone_number?.area_code } } fn main() { let p = Person { job: Some(Job { phone_number: Some(PhoneNumber { area_code: Some(61), number: 439222222, }), }), }; assert_eq!(p.work_phone_area_code(), Some(61)); }
コンビネータ: map
matchはOptionを処理するのに使えます。しかし、特に引数の値が有効である必要がある
場合、これが億劫だと思うかもしれません。このような場合、コンビネータ
でモジュール的に制御フローを管理することができます。
Optionは、Some -> SomeやNone -> Noneのような単純な処理を扱うためにmap()という
ビルトインメソッドを持っていて、これを柔軟に連結することができます。
以下の例では、process()は以前の関数のすべてをコンパクトに置き換えることができます。
#![allow(dead_code)] #[derive(Debug)] enum Food { Apple, Carrot, Potato } #[derive(Debug)] struct Peeled(Food); #[derive(Debug)] struct Chopped(Food); #[derive(Debug)] struct Cooked(Food); // 食べ物の皮を剥く。もし何もなければ、`None`を返す。 // その他の場合は、剥いた食べ物を返す。 fn peel(food: Option<Food>) -> Option<Peeled> { match food { Some(food) => Some(Peeled(food)), None => None, } } // 食べ物を切る。もし何もなければ、`None`を返す。 // その他の場合は、切った食べ物を返す。 fn chop(peeled: Option<Peeled>) -> Option<Chopped> { match peeled { Some(Peeled(food)) => Some(Chopped(food)), None => None, } } // 食べ物を料理する。ここでは`match`の代わりに`map()`で処理する。 fn cook(chopped: Option<Chopped>) -> Option<Cooked> { chopped.map(|Chopped(food)| Cooked(food)) } // 食べ物を連続で剥き、切り、料理する。 // `map()`を連結してシンプルなコードを実現している。 fn process(food: Option<Food>) -> Option<Cooked> { food.map(|f| Peeled(f)) .map(|Peeled(f)| Chopped(f)) .map(|Chopped(f)| Cooked(f)) } // 食べる前に食べ物があるか確認する必要があります! fn eat(food: Option<Cooked>) { match food { Some(food) => println!("Mmm. I love {:?}", food), None => println!("Oh no! It wasn't edible."), } } fn main() { let apple = Some(Food::Apple); let carrot = Some(Food::Carrot); let potato = None; let cooked_apple = cook(chop(peel(apple))); let cooked_carrot = cook(chop(peel(carrot))); // `process()`でシンプルに調理してみましょう let cooked_potato = process(potato); eat(cooked_apple); eat(cooked_carrot); eat(cooked_potato); }
こちらも参照:
コンビネータ: and_then
map()はmatch文を連結してシンプルにするものでした。しかし、
Option<T>を返す関数でmap()を使えば、ネストされたOption<Option<t>>
が必要になります。この場合、複数の呼び出しを連続で使うと混乱を招きます。
ここで、他の言語ではflatmapとして知られている、別のコンビネータ
and_then()の登場です。
and_then()は引数として渡された関数にラップされた値を返しますが、それがNoneなら、
Noneを返します。
In the following example, cookable_v2() results in an Option<Food>.
Using map() instead of and_then() would have given an
Option<Option<Food>>, which is an invalid type for eat().
#![allow(dead_code)] #[derive(Debug)] enum Food { CordonBleu, Steak, Sushi } #[derive(Debug)] enum Day { Monday, Tuesday, Wednesday } // 寿司の材料を持っていません。 fn have_ingredients(food: Food) -> Option<Food> { match food { Food::Sushi => None, _ => Some(food), } } // コルドン・ブルー(訳注: カツレツのようなもの)のレシピを持っていません。 fn have_recipe(food: Food) -> Option<Food> { match food { Food::CordonBleu => None, _ => Some(food), } } // 料理を作るには、材料とレシピの両方が必要です。 // `match`でロジックの流れを表す。 fn cookable_v1(food: Food) -> Option<Food> { match have_recipe(food) { None => None, Some(food) => match have_ingredients(food) { None => None, Some(food) => Some(food), }, } } // `and_then()`でコンパクトに書き直すことができます。 fn cookable_v2(food: Food) -> Option<Food> { have_recipe(food).and_then(have_ingredients) } fn eat(food: Food, day: Day) { match cookable_v2(food) { Some(food) => println!("Yay! On {:?} we get to eat {:?}.", day, food), None => println!("Oh no. We don't get to eat on {:?}?", day), } } fn main() { let (cordon_bleu, steak, sushi) = (Food::CordonBleu, Food::Steak, Food::Sushi); eat(cordon_bleu, Day::Monday); eat(steak, Day::Tuesday); eat(sushi, Day::Wednesday); }
こちらも参照:
Result
Resultは値の不在をエラーに置き換えたOption型
の上位互換です。
つまり、Result<T, E>は2つの結果を持つことができます。
Ok(T):Tが存在する場合Err(E):Eというエラーが見つかった場合
利便性のため、期待する値をOk、予期せぬ値をErrに入れます。
Optionと同じように、Resultは多くのメソッドを持っています。例えば、
unwrap()はT型の値を返すか、panicします。また、条件処理のために、
Optionと同じようなコンビネータがResultにもあります。
Rustを使っていると、おそらく、parse()のようにResult型を返す
ようなメソッドに出会うでしょう。文字列をいつも他の型に変換できるとは限らない
ため、parse()は失敗を処理するためにResultを使っています。
成功した場合と失敗した場合の文字列のparse()の振る舞いを見てみましょう。
fn multiply(first_number_str: &str, second_number_str: &str) -> i32 { // `unwrap()`を使って数値を取り出してみましょう。 let first_number = first_number_str.parse::<i32>().unwrap(); let second_number = second_number_str.parse::<i32>().unwrap(); first_number * second_number } fn main() { let twenty = multiply("10", "2"); println!("double is {}", twenty); let tt = multiply("t", "2"); println!("double is {}", tt); }
parse()が失敗した場合は、unwrap()でpanicを起こしてプログラムを終了しました。
さらに、panicは不快なエラーメッセージを返しました。
エラーメッセージの質を改善するため、返り値の型をさらに特定し、明示的に エラーを処理する必要があります。
mainでResultを使う
明示的に指定することで、Result型をmain関数で使うこともできます。
典型的なmain関数はこのような形です。
fn main() { println!("Hello World!"); }
しかし、mainは返り値としてResultを使うこともできます。main関数内でエラーが
起これば、エラーコードを返し、デバッグプリントでエラーを報告します。(Debug
トレイトを使います。) 以下の例では、次の例では、そのようなシナリオを紹介し、
後の節で扱う内容にも触れます。
use std::num::ParseIntError; fn main() -> Result<(), ParseIntError> { let number_str = "10"; let number = match number_str.parse::<i32>() { Ok(number) => number, Err(e) => return Err(e), }; println!("{}", number); Ok(()) }
Resultのmap
前の例のmultiplyでのパニックは実用コードのために作られていません。
普通、呼び出し元にエラーを返し、エラー処理を呼び出し元に任せます。
まず、どのようなタイプのエラーがあるのか知る必要があります。Errの型を決めるため、
FromStrトレイトをi32に実装することで提供されているparse()
関数を見てみます。すると、Errの型はParseIntErrorだと
定義されていることがわかります。
以下の例では、愚直にmatch文を使っていますが、これは面倒です。
use std::num::ParseIntError; // 返り値の型を書き直し、`unwrap()`を使わずパターンマッチしています。 fn multiply(first_number_str: &str, second_number_str: &str) -> Result<i32, ParseIntError> { match first_number_str.parse::<i32>() { Ok(first_number) => { match second_number_str.parse::<i32>() { Ok(second_number) => { Ok(first_number * second_number) }, Err(e) => Err(e), } }, Err(e) => Err(e), } } fn print(result: Result<i32, ParseIntError>) { match result { Ok(n) => println!("n is {}", n), Err(e) => println!("Error: {}", e), } } fn main() { // これはまだ使えます。 let twenty = multiply("10", "2"); print(twenty); // これは有用なエラーメッセージを返すようになりました。 let tt = multiply("t", "2"); print(tt); }
幸運なことに、Optionのmapやand_thenなどのコンビネータはResultにも
実装されています。Resultに完全な一覧があります。
use std::num::ParseIntError; // `Option`のように、`map()`コンビネータが使えます。 // この関数は上と同じ動作をします。 // 値が有効な場合nを渡し、その他の場合はエラーを渡す。 fn multiply(first_number_str: &str, second_number_str: &str) -> Result<i32, ParseIntError> { first_number_str.parse::<i32>().and_then(|first_number| { second_number_str.parse::<i32>().map(|second_number| first_number * second_number) }) } fn print(result: Result<i32, ParseIntError>) { match result { Ok(n) => println!("n is {}", n), Err(e) => println!("Error: {}", e), } } fn main() { // これはまだ使えます。 let twenty = multiply("10", "2"); print(twenty); // これは有用なエラーメッセージを返すようになりました。 let tt = multiply("t", "2"); print(tt); }
Resultのエイリアス
特定のResult型を何回も使い回すときはどうでしょう?
前に言ったようにRustでは型エイリアスが使えるため、
特定のResult型を再定義することができます。
モジュールレベルで型エイリアスを作ると非常に便利です。特定のモジュールでは
同じErr型を使うことが多いからです。これによってすべての関連したResult
に一つの型エイリアスを使うことができます。stdライブラリもこのような型io::Result
を定義しています!
これは構文を紹介するための簡単な例です。
use std::num::ParseIntError; // `ParseIntError`を持つ`Result`型のジェネリックなエイリアス type AliasedResult<T> = Result<T, ParseIntError>; // 特定のエラー型を持つ`Result`型を使ってみましょう。 fn multiply(first_number_str: &str, second_number_str: &str) -> AliasedResult<i32> { first_number_str.parse::<i32>().and_then(|first_number| { second_number_str.parse::<i32>().map(|second_number| first_number * second_number) }) } // ここでコード量を節約できます。 fn print(result: AliasedResult<i32>) { match result { Ok(n) => println!("n is {}", n), Err(e) => println!("Error: {}", e), } } fn main() { print(multiply("10", "2")); print(multiply("t", "2")); }
こちらも参照:
早期のリターン
前の例で、コンビネータを使って明示的にエラーを処理していました。
もう一つの方法は、match文と早期のリターンを組み合わせて使うことです。
つまり、単純に一つエラーが起こればそこでエラーを返すということです。 しばしば、この方が読みやすく、書きやすいコードになります。早期のリターン を使って前の例を書き直しました。
use std::num::ParseIntError; fn multiply(first_number_str: &str, second_number_str: &str) -> Result<i32, ParseIntError> { let first_number = match first_number_str.parse::<i32>() { Ok(first_number) => first_number, Err(e) => return Err(e), }; let second_number = match second_number_str.parse::<i32>() { Ok(second_number) => second_number, Err(e) => return Err(e), }; Ok(first_number * second_number) } fn print(result: Result<i32, ParseIntError>) { match result { Ok(n) => println!("n is {}", n), Err(e) => println!("Error: {}", e), } } fn main() { print(multiply("10", "2")); print(multiply("t", "2")); }
ここまでで、コンビネータや早期のリターンを使って、明示的にエラーを処理する方法を 見てきました。しかし、パニックを避け、明示的にすべてのエラーを処理するのは面倒です。
次の節では、unwrapのようにシンプルに、しかしpanicをせずにエラーを処理する?
を導入します。
?の導入
時々、panicなしに、unwrapのような単純さが欲しくなることがあります。
今まで、変数を取得するためだけに、ますます深くネストすることが必要でした。
これをなくすのが?の目的です。
Errを見つけると、このような挙動を取ることができます。
panic!。これはできるだけ避けるべきです。return。Errは処理できないことを意味するため、returnする。
?はほとんど1 unwrapのpanicをreturnに変えたもとと等価です。
先程の例がどれだけシンプルになるか見てみましょう。
use std::num::ParseIntError; fn multiply(first_number_str: &str, second_number_str: &str) -> Result<i32, ParseIntError> { let first_number = first_number_str.parse::<i32>()?; let second_number = second_number_str.parse::<i32>()?; Ok(first_number * second_number) } fn print(result: Result<i32, ParseIntError>) { match result { Ok(n) => println!("n is {}", n), Err(e) => println!("Error: {}", e), } } fn main() { print(multiply("10", "2")); print(multiply("t", "2")); }
try!マクロ
?がなかった頃、同じ機能をtry!マクロが持っていました。現在は?演算子が推奨されていますが、
古いコードを見ていると、try!に遭遇するかもしれません。前の例と同じmultiply関数をtry!で
実装してみます。
// この例を警告なしに実行するにはCargo.tomlの`[package]`節にある`edition`フィールドを // "2015"に変更する必要があります。 use std::num::ParseIntError; fn multiply(first_number_str: &str, second_number_str: &str) -> Result<i32, ParseIntError> { let first_number = try!(first_number_str.parse::<i32>()); let second_number = try!(second_number_str.parse::<i32>()); Ok(first_number * second_number) } fn print(result: Result<i32, ParseIntError>) { match result { Ok(n) => println!("n is {}", n), Err(e) => println!("Error: {}", e), } } fn main() { print(multiply("10", "2")); print(multiply("t", "2")); }
詳しくは他の?の使い方を参照してください。
複数のエラー型
Resultが他のResultと、Optionが他のOptionと連携が取れるため、
前の例は非常に便利でした。
さて、時々OptionとResult、Result<T, Error1>とResult<T, Error2>の間で
連携を取る必要があるときがあります。この場合、異なるエラー型を簡単で構成可能
に管理する必要があります。
以下のコードでは、Vec::firstはOptionを、parse::<i32>はResult<i32, ParseIntError>
を返すため、2つのunwrapがそれぞれ異なるエラーを処理しています。
fn double_first(vec: Vec<&str>) -> i32 { let first = vec.first().unwrap(); // Generate error 1 2 * first.parse::<i32>().unwrap() // Generate error 2 } fn main() { let numbers = vec!["42", "93", "18"]; let empty = vec![]; let strings = vec!["tofu", "93", "18"]; println!("The first doubled is {}", double_first(numbers)); println!("The first doubled is {}", double_first(empty)); // エラー1: 入力ベクターが空です。 println!("The first doubled is {}", double_first(strings)); // エラー2: 入力を数値に変換できません。 }
次の節から、このような問題を処理するいくつかの方法を見ていきます。
ResultをOptionから引き出す
この問題の最も簡単な解決策は、どちらかがもう片方を埋め込むことです。
use std::num::ParseIntError; fn double_first(vec: Vec<&str>) -> Option<Result<i32, ParseIntError>> { vec.first().map(|first| { first.parse::<i32>().map(|n| 2 * n) }) } fn main() { let numbers = vec!["42", "93", "18"]; let empty = vec![]; let strings = vec!["tofu", "93", "18"]; println!("The first doubled is {:?}", double_first(numbers)); println!("The first doubled is {:?}", double_first(empty)); // エラー1: 入力ベクターが空です println!("The first doubled is {:?}", double_first(strings)); // エラー2: 入力を数値に変換できません }
エラーが起きた時に途中で処理を止めたい場合(?などを使って)
OptionはそのままNoneを返します。 コンビネータがResultとOptionを入れ替えるのに
役立ちます。
use std::num::ParseIntError; fn double_first(vec: Vec<&str>) -> Result<Option<i32>, ParseIntError> { let opt = vec.first().map(|first| { first.parse::<i32>().map(|n| 2 * n) }); opt.map_or(Ok(None), |r| r.map(Some)) } fn main() { let numbers = vec!["42", "93", "18"]; let empty = vec![]; let strings = vec!["tofu", "93", "18"]; println!("The first doubled is {:?}", double_first(numbers)); println!("The first doubled is {:?}", double_first(empty)); println!("The first doubled is {:?}", double_first(strings)); }
エラー型を定義する
すべてのエラー型を一つの型で包んだ方がシンプルな場合もあります。 今回は、これを独自のエラーでやってみることにします。
Rustでは独自のエラー型を作成することができます。 一般的に、「良い」エラー型は:
- 異なるエラーを同じ型で表す
- わかりやすいエラーメッセージを提供する
- 他の型と比較しやすい
- 良い例:
Err(EmptyVec) - 悪い例:
Err("Please use a vector with at least one element".to_owned())
- 良い例:
- エラーについての情報を保持する
- 良い例:
Err(BadChar(c, position)) - 悪い例:
Err("+ cannot be used here".to_owned())
- 良い例:
- 他のエラーと連携が取れる
use std::fmt; type Result<T> = std::result::Result<T, DoubleError>; // 独自のエラー型を定義する。これはエラー処理の方法によってカスタマイズできる。 // ここで独自のエラーを起こしたり、下層のエラー実装を持ち越したり、その間で // なにか処理をしたりすることができる。 #[derive(Debug, Clone)] struct DoubleError; // エラーの生成とどのようにそれが出力されるかということとは関係ありません。 // なので、出力のスタイルに複雑で乱雑なロジックを書く必要はありません。 // // ここにはエラーに関するどんなデータも含まれていないことに注意してください。これは、 // どの処理が失敗したのかを知ることはできないということを意味します。 impl fmt::Display for DoubleError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "invalid first item to double") } } fn double_first(vec: Vec<&str>) -> Result<i32> { vec.first() // エラーを独自の新しい型に変換します。 .ok_or(DoubleError) .and_then(|s| { s.parse::<i32>() // ここも新しい型に変換します。 .map_err(|_| DoubleError) .map(|i| 2 * i) }) } fn print(result: Result<i32>) { match result { Ok(n) => println!("The first doubled is {}", n), Err(e) => println!("Error: {}", e), } } fn main() { let numbers = vec!["42", "93", "18"]; let empty = vec![]; let strings = vec!["tofu", "93", "18"]; print(double_first(numbers)); print(double_first(empty)); print(double_first(strings)); }
エラーをBoxに入れる
シンプルなコードを書くために、Boxにエラーデータの参照を入れることもできます。
欠点は、実行時にしかエラー型がわからず、静的に決定できないことです。
標準ライブラリは、Fromで、トレイトオブジェクトBox<Error>をErrorトレイト
を実装したすべての型に変換する実装を提供しています。
use std::error; use std::fmt; // エイリアスを`Box<error::Error>`に変更する type Result<T> = std::result::Result<T, Box<dyn error::Error>>; #[derive(Debug, Clone)] struct EmptyVec; impl fmt::Display for EmptyVec { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "invalid first item to double") } } impl error::Error for EmptyVec {} fn double_first(vec: Vec<&str>) -> Result<i32> { vec.first() .ok_or_else(|| EmptyVec.into()) // Converts to Box .and_then(|s| { s.parse::<i32>() .map_err(|e| e.into()) // Converts to Box .map(|i| 2 * i) }) } fn print(result: Result<i32>) { match result { Ok(n) => println!("The first doubled is {}", n), Err(e) => println!("Error: {}", e), } } fn main() { let numbers = vec!["42", "93", "18"]; let empty = vec![]; let strings = vec!["tofu", "93", "18"]; print(double_first(numbers)); print(double_first(empty)); print(double_first(strings)); }
こちらも参照:
?の他の使い方
前の例では、parseを即座にmapに渡して、エラーをBoxに格納していました。
.and_then(|s| s.parse::<i32>()
.map_err(|e| e.into())
これは一般的な操作なので、利便性のため省略することができます。
悲しいかな、and_thenは柔軟性が低いため、できません。しかし、
?を使えばできます。
?はunwrapやreturn Err(err)と同じだと説明しました。これは大体合って
いますが、実際にはunwrapやreturn Err(From::from(err))です。
From::fromは他の型に変更するツールであるため、これは?によって返り値の型
に変換できることを意味しています。
ここで、前の例を?を使って書き直してみましょう。結果的に、map_errは
From::fromがエラー型に実装されていることから不要になります。
use std::error; use std::fmt; // エイリアスを`Box<dyn error::Error>`に変更します。 type Result<T> = std::result::Result<T, Box<dyn error::Error>>; #[derive(Debug)] struct EmptyVec; impl fmt::Display for EmptyVec { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "invalid first item to double") } } impl error::Error for EmptyVec {} // 前と同じ構造ですが、すべての`Results`と`Options`を一つにつながず、 // `?`から値を変数に代入しています。 fn double_first(vec: Vec<&str>) -> Result<i32> { let first = vec.first().ok_or(EmptyVec)?; let parsed = first.parse::<i32>()?; Ok(2 * parsed) } fn print(result: Result<i32>) { match result { Ok(n) => println!("The first doubled is {}", n), Err(e) => println!("Error: {}", e), } } fn main() { let numbers = vec!["42", "93", "18"]; let empty = vec![]; let strings = vec!["tofu", "93", "18"]; print(double_first(numbers)); print(double_first(empty)); print(double_first(strings)); }
これで非常にきれいになりました。もともとのpanicと比べると、返り値の型が
Result担ったことを除いて、unwrapが?に変わっただけであることがわかります。
また、Resultはトップレベルで分解する必要があります。
こちらも参照:
エラーをラップする
複数のエラーをボックス化するもう一つの方法は、独自の型でエラーを ラップすることです。
use std::error; use std::num::ParseIntError; use std::fmt; type Result<T> = std::result::Result<T, DoubleError>; #[derive(Debug)] enum DoubleError { EmptyVec, // 変換エラーに関する実装を持ち越します。 // さらなる情報を得るため、型にデータを追加します。 Parse(ParseIntError), } impl fmt::Display for DoubleError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { DoubleError::EmptyVec => write!(f, "please use a vector with at least one element"), // これはラッパーであるため、元の型の関数を呼び出します。 DoubleError::Parse(ref e) => e.fmt(f), } } } impl error::Error for DoubleError { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match *self { DoubleError::EmptyVec => None, // 下層の実装のエラー型を返します。トレイとオブジェクトは暗示的に // `&error::Error`にキャストされます。下層の実装はすでに`Error` // トレイトを実装しているため、これは動作します。 DoubleError::Parse(ref e) => Some(e), } } } // `ParseIntError`から`DoubleError`への変換を実装する。 // `?`から呼び出されたときに`ParseIntError`を // `DoubleError`に変換するのに使われる。 impl From<ParseIntError> for DoubleError { fn from(err: ParseIntError) -> DoubleError { DoubleError::Parse(err) } } fn double_first(vec: Vec<&str>) -> Result<i32> { let first = vec.first().ok_or(DoubleError::EmptyVec)?; let parsed = first.parse::<i32>()?; Ok(2 * parsed) } fn print(result: Result<i32>) { match result { Ok(n) => println!("The first doubled is {}", n), Err(e) => println!("Error: {}", e), } } fn main() { let numbers = vec!["42", "93", "18"]; let empty = vec![]; let strings = vec!["tofu", "93", "18"]; print(double_first(numbers)); print(double_first(empty)); print(double_first(strings)); }
これは少し典型コードが多くなる上、すべてのアプリケーションに必要となるわけではありません。 個の典型コードを少なくするためのライブラリがいくつかあります。
こちらも参照:
イテレーションでResultを表す
Iter::mapは失敗する可能性があります。例えば:
fn main() { let strings = vec!["tofu", "93", "18"]; let numbers: Vec<_> = strings .into_iter() .map(|s| s.parse::<i32>()) .collect(); println!("Results: {:?}", numbers); }
これを処理する方法を見ていきましょう。
filter_map()で失敗したものを無視する
filter_mapはNoneである結果をフィルタにかける。
fn main() { let strings = vec!["tofu", "93", "18"]; let numbers: Vec<_> = strings .into_iter() .map(|s| s.parse::<i32>()) .filter_map(Result::ok) .collect(); println!("Results: {:?}", numbers); }
collect()でコードを失敗にする
ResultはFromIterを実装しているため、Result型のベクター(Vec<Result<T, E>>)
をベクター型のResult(Result<Vec<T>, E>)に変換することができます。
そして、Result::Errが見つかれば処理を終了できます。
fn main() { let strings = vec!["tofu", "93", "18"]; let numbers: Result<Vec<_>, _> = strings .into_iter() .map(|s| s.parse::<i32>()) .collect(); println!("Results: {:?}", numbers); }
Optionでも同じことができます。
すべての利用可能な値を集めてpartition()で失敗を振り分ける。
fn main() { let strings = vec!["tofu", "93", "18"]; let (numbers, errors): (Vec<_>, Vec<_>) = strings .into_iter() .map(|s| s.parse::<i32>()) .partition(Result::is_ok); println!("Numbers: {:?}", numbers); println!("Errors: {:?}", errors); }
結果を見たら、これがまだResultで包まれていることに気付くでしょう。
もう少し典型コードが必要です。
fn main() { let strings = vec!["tofu", "93", "18"]; let (numbers, errors): (Vec<_>, Vec<_>) = strings .into_iter() .map(|s| s.parse::<i32>()) .partition(Result::is_ok); let numbers: Vec<_> = numbers.into_iter().map(Result::unwrap).collect(); let errors: Vec<_> = errors.into_iter().map(Result::unwrap_err).collect(); println!("Numbers: {:?}", numbers); println!("Errors: {:?}", errors); }
Stdライブラリの型
stdライブラリはプリミティブを劇的に拡張する多くのカスタム型を
提供しています。これはそのうちの一つです。
"hello world"のような可変長文字列String[1, 2, 3]のような可変長配列VectorOption<i32>型- エラー制御をする
Result<i32, i32>型 - ヒープのポインタ
Box<i32>型
こちらも参照:
ボックス、スタック、ヒープ
Rustではすべての変数がデフォルトでスタックに保存されます。しかし、Box<T>を
使うことで、値をヒープに保存することができます。Box<T>はヒープ上に保存された
型Tの値をさすスマートポインタです。Boxがスコープを出ると、デストラクタが
呼び出されることで、中の値も破棄され、ヒープメモリが開放されます。
Box内の値は*演算子で参照できます。これによって、間接参照のコストが
低くなります。
use std::mem; #[allow(dead_code)] #[derive(Debug, Clone, Copy)] struct Point { x: f64, y: f64, } // 長方形は左上の点と右下の点によって定義できます。 #[allow(dead_code)] struct Rectangle { top_left: Point, bottom_right: Point, } fn origin() -> Point { Point { x: 0.0, y: 0.0 } } fn boxed_origin() -> Box<Point> { // ヒープ上に作られた原点を指すPointのポインタを返す Box::new(Point { x: 0.0, y: 0.0 }) } fn main() { // (すべての型注釈は必要ないものです) // スタックに保存された変数 let point: Point = origin(); let rectangle: Rectangle = Rectangle { top_left: origin(), bottom_right: Point { x: 3.0, y: -4.0 } }; // ヒープに保存された長方形 let boxed_rectangle: Box<Rectangle> = Box::new(Rectangle { top_left: origin(), bottom_right: Point { x: 3.0, y: -4.0 }, }); // 関数の出力をBoxする let boxed_point: Box<Point> = Box::new(origin()); // 二重参照 let box_in_a_box: Box<Box<Point>> = Box::new(boxed_origin()); println!("Point occupies {} bytes on the stack", // Pointはスタック上の{}バイトを占有しています mem::size_of_val(&point)); println!("Rectangle occupies {} bytes on the stack", // Rectangleはスタック上の{}バイトを占有しています mem::size_of_val(&rectangle)); // Boxのサイズはポインタのサイズと等しい println!("Boxed point occupies {} bytes on the stack", // BoxされたPointはスタック上の{}バイトを占有しています mem::size_of_val(&boxed_point)); println!("Boxed rectangle occupies {} bytes on the stack", // BoxされたRectangleはスタック上の{}バイトを占有しています mem::size_of_val(&boxed_rectangle)); println!("Boxed box occupies {} bytes on the stack", // BoxされたBoxはスタック上の{}バイトを占有しています mem::size_of_val(&box_in_a_box)); // `boxed_point`内のデータを`unboxed_point`にコピーする let unboxed_point: Point = *boxed_point; println!("Unboxed point occupies {} bytes on the stack", // BoxされていないPointはスタック上の{}バイトを占有しています mem::size_of_val(&unboxed_point)); }
ベクター
ベクターは可変長の配列です。スライスのように、コンパイル時にはサイズがわかりませんが、 自由にサイズを伸縮できます。ベクターは次の3要素からなります。
- データへのポインタ
- 長さ
- 容量
容量は、ベクターにどれだけのメモリが割り当てられているかを表します。容量を超えない 範囲で、ベクターの長さを伸ばすことができます。これを超える長さが必要になった時、 ベクターはもっと多い容量の場所へ再確保されます。
fn main() { // イテレータはベクターに変換することができます。 let collected_iterator: Vec<i32> = (0..10).collect(); println!("Collected (0..10) into: {:?}", collected_iterator); // `vec!`マクロでベクターを初期化できます。 let mut xs = vec![1i32, 2, 3]; println!("Initial vector: {:?}", xs); // ベクターに新しい要素を入れる println!("Push 4 into the vector"); xs.push(4); println!("Vector: {:?}", xs); // エラー! 不変なベクターは伸ばすことができません。 collected_iterator.push(0); // FIXME ^ この行をコメントアウトしてください // `len`メソッドで現在のベクターの要素数を取得できます。 println!("Vector length: {}", xs.len()); // 角括弧で要素にアクセスできます。(インデックスは0から始まります) println!("Second element: {}", xs[1]); // `pop`はベクターから最後の要素を消し、それを返します。 println!("Pop last element: {:?}", xs.pop()); // 長さ以上のインデックスを指定するとパニックします。 println!("Fourth element: {}", xs[3]); // FIXME ^ この行をコメントアウトしてください // `Vector`は簡単にイテレーションできます。 println!("Contents of xs:"); for x in xs.iter() { println!("> {}", x); } // enumerateを使って要素のインデックスを変数(`i`)に // とってイテレーションできます。 for (i, x) in xs.iter().enumerate() { println!("In position {} we have value {}", i, x); } // `iter_mut`のおかげで、可変な`Vector`を変更しながら // イテレーションできます。 for x in xs.iter_mut() { *x *= 3; } println!("Updated vector: {:?}", xs); }
Vecのメソッドをさらに知りたいときはstd::vecモジュール
のドキュメントを参照してください。
文字列
Rustには2つのタイプの文字列String、&strがあります。
StringはVec<u8>でバイト列が保存されていますが、UTF-8が使えることは保証されて
います。Stringはヒープに保存されるので、可変長で、ヌル文字で終わりません。
&strはスライス&[u8]であり、いつも有効なUTF-8文字列を参照しています。
そしてこれは&[T]がVec<T>に変換できるように、Stringに変換できます。
fn main() { // (すべての型注釈は必要ないものです) // 読み取りメモリに保存された文字列へのポインタ let pangram: &'static str = "the quick brown fox jumps over the lazy dog"; // 茶色く素早い狐がのろまな犬を飛び越える println!("Pangram: {}", pangram); // 単語を逆順でイテレートします。新しくメモリの確保はされません。 println!("Words in reverse"); for word in pangram.split_whitespace().rev() { println!("> {}", word); } // 文字をすべてベクターにコピーし、ソートし、重複を消す。 let mut chars: Vec<char> = pangram.chars().collect(); chars.sort(); chars.dedup(); // 空の可変長な`String`を作る let mut string = String::new(); for c in chars { // 文字列の最後に文字を挿入する string.push(c); // 文字列の最後に文字列を挿入する string.push_str(", "); } // 文字列をトリミングすると、元の文字列のスライスを作るため、 // 新しくメモリ確保はされない。 let chars_to_trim: &[char] = &[' ', ',']; let trimmed_str: &str = string.trim_matches(chars_to_trim); println!("Used characters: {}", trimmed_str); // 文字列をヒープに格納する。 let alice = String::from("I like dogs"); // 新しくメモリ確保して、置換後の文字列を格納する。 let bob: String = alice.replace("dog", "cat"); println!("Alice says: {}", alice); println!("Bob says: {}", bob); }
strとStringのさらなるメソッドについてはstd::strや
std::stringモジュールのドキュメントを参照してください。
リテラルとエスケープ
文字列のリテラルを書き、特殊文字をその中に入れる方法は複数あり、そのすべてが
、最も書きやすく、扱いやすい&strになります。同様にバイト文字列リテラルの書き方
は複数ありますが、すべて&[u8; N]になります。
一般的に特殊文字はバックスラッシュ\でエスケープできます。これを使えば、
出力できない文字や、打ち方を知らない文字でも表すことができます。バックスラッシュ
のリテラルは\\と表します。
文字列や文字のリテラルの終端を表す文字は、"\""や'\''で表します。
fn main() { // 16進数で文字を一つ一つ書くこともできます... let byte_escape = "I'm writing \x52\x75\x73\x74!"; println!("What are you doing\x3F (\\x3F means ?) {}", byte_escape); // ...Unicodeコードポイントも使えます。 let unicode_codepoint = "\u{211D}"; let character_name = "\"DOUBLE-STRUCK CAPITAL R\""; println!("Unicode character {} (U+211D) is called {}", unicode_codepoint, character_name ); let long_string = "String literals // 文字列リテラルは改行やインデントもエスケープできます。 can span multiple lines. The linebreak and indentation here ->\ <- can be escaped too!"; println!("{}", long_string); }
エスケープに使われる文字をたくさん使った文字列を書きたいときもあります。 この場合はRaw文字列リテラルを使います。
fn main() { let raw_str = r"Escapes don't work here: \x3F \u{211D}"; println!("{}", raw_str); // Raw1文字列にクオートを使うときは、#で囲みます。 let quotes = r#"And then I said: "There is no escape!""#; println!("{}", quotes); // "も#も使う場合は、さらに#を重ねてください。 // #の重ねられる数に上限はありません。 let longer_delimiter = r###"A string with "# in it. And even "##!"###; println!("{}", longer_delimiter); }
UTF-8出ない文字列を使いたいですか? (strとStringはUTF-8でないといけません)
またはバイト列を文字列として使いたいですか? バイト文字列が使えます!
use std::str; fn main() { // これは`&str`ではありません。 let bytestring: &[u8; 21] = b"this is a byte string"; // バイト列は`Display`トレイトを持っていないので、出力は多少難しいです。 println!("A byte string: {:?}", bytestring); // バイト文字列はバイトエスケープができます... let escaped = b"\x52\x75\x73\x74 as bytes"; // ...しかしユニコードエスケープはできません // let escaped = b"\u{211D} is not allowed"; println!("Some escaped bytes: {:?}", escaped); // Rawバイト列もRaw文字列のように使えます。 let raw_bytestring = br"\u{211D} is not escaped here"; println!("{:?}", raw_bytestring); // バイト列から`str`への変換は時々失敗します。 if let Ok(my_str) = str::from_utf8(raw_bytestring) { println!("And the same as text: '{}'", my_str); } let _quotes = br#"You can also use "fancier" formatting, \ like with normal raw strings"#; // バイト文字列はUTF-8である必要はありません。 let shift_jis = b"\x82\xe6\x82\xa8\x82\xb1\x82\xbb"; // Shift-JISで"ようこそ" // しかし`str`には変換できません match str::from_utf8(shift_jis) { Ok(my_str) => println!("Conversion successful: '{}'", my_str), Err(e) => println!("Conversion failed: {:?}", e), }; }
エンコードの違う文字列を変換するにはencodingクレートを使用します。
リテラルの書き方やエスケープシーケンスの詳細はRustリファレンスの 'Tokens'章を参照してください。
Option
時々panic!を使わずにエラーを処理したいことがあります。
これはOptionenumを使うことで実現できます。
Option<T>は2つの列挙子を持ちます。
Noneは値がないことをあらわし、Some(value)はT型のvalueを持つタプル構造体です。
// `panic!`しない整数の割り算 fn checked_division(dividend: i32, divisor: i32) -> Option<i32> { if divisor == 0 { // 失敗したときは`None`列挙子を返す None } else { // 結果は`Some`列挙子に入れて返します Some(dividend / divisor) } } // この関数は割り算が成功したか確認します。 fn try_division(dividend: i32, divisor: i32) { // `Option`の値は他のenumと同じようにパターンマッチできる。 match checked_division(dividend, divisor) { None => println!("{} / {} failed!", dividend, divisor), Some(quotient) => { println!("{} / {} = {}", dividend, divisor, quotient) }, } } fn main() { try_division(4, 2); try_division(1, 0); // `None`を変数に代入する時は型注釈が必要です let none: Option<i32> = None; let _equivalent_none = None::<i32>; let optional_float = Some(0f32); // `Some`列挙子でラップされた値を取り出す。 println!("{:?} unwraps to {:?}", optional_float, optional_float.unwrap()); // `None`列挙子だった場合は`panic!`する println!("{:?} unwraps to {:?}", none, none.unwrap()); }
Result
Optionenumで失敗する可能性のある関数から値を返すことができました。
その場合、失敗したときはNoneを返します。しかし、しばしばなぜ関数が
失敗したのかが重要になってくることがあります。これはResultenumで表現します。
Result<T, E>enumは2つの列挙子を持ちます。
Ok(value)は成功時に返され、T型の値valueには結果が入ります。Err(why)は失敗時に返され、E型の値whyには失敗の原因が入ります。
mod checked { // 数学的な「エラー」をキャッチする #[derive(Debug)] pub enum MathError { DivisionByZero, NonPositiveLogarithm, NegativeSquareRoot, } pub type MathResult = Result<f64, MathError>; pub fn div(x: f64, y: f64) -> MathResult { if y == 0.0 { // この操作は失敗なので、失敗の原因を`Err` // に入れて返します。 Err(MathError::DivisionByZero) } else { // この操作は成功なので、結果を`Ok`に入れて返します。 Ok(x / y) } } pub fn sqrt(x: f64) -> MathResult { if x < 0.0 { Err(MathError::NegativeSquareRoot) } else { Ok(x.sqrt()) } } pub fn ln(x: f64) -> MathResult { if x <= 0.0 { Err(MathError::NonPositiveLogarithm) } else { Ok(x.ln()) } } } // `op(x, y)`と`sqrt(ln(x / y))`は等価です fn op(x: f64, y: f64) -> f64 { // 3段階matchピラミッドです! match checked::div(x, y) { Err(why) => panic!("{:?}", why), Ok(ratio) => match checked::ln(ratio) { Err(why) => panic!("{:?}", why), Ok(ln) => match checked::sqrt(ln) { Err(why) => panic!("{:?}", why), Ok(sqrt) => sqrt, }, }, } } fn main() { // 失敗しますか? println!("{}", op(1.0, 10.0)); }
?
Resultの処理をチェーンすると、だらしないです。幸運なことに、?演算子を使えば
きれいに書くことができます。Resultを返す?を式の最後につけると、match式のように、
Err(err)ならばErr(From::from(err))を返して関数を終了し、Ok(ok)ならばokを返します。
mod checked { #[derive(Debug)] enum MathError { DivisionByZero, NonPositiveLogarithm, NegativeSquareRoot, } type MathResult = Result<f64, MathError>; fn div(x: f64, y: f64) -> MathResult { if y == 0.0 { Err(MathError::DivisionByZero) } else { Ok(x / y) } } fn sqrt(x: f64) -> MathResult { if x < 0.0 { Err(MathError::NegativeSquareRoot) } else { Ok(x.sqrt()) } } fn ln(x: f64) -> MathResult { if x <= 0.0 { Err(MathError::NonPositiveLogarithm) } else { Ok(x.ln()) } } // 中間関数 fn op_(x: f64, y: f64) -> MathResult { // `div`が失敗したら、`DivisionByZero`が`return`される let ratio = div(x, y)?; // `ln`が失敗したら、`NonPositiveLogarithm`が`return`される let ln = ln(ratio)?; sqrt(ln) } pub fn op(x: f64, y: f64) { match op_(x, y) { Err(why) => panic!(match why { MathError::NonPositiveLogarithm => "logarithm of non-positive number", MathError::DivisionByZero => "division by zero", MathError::NegativeSquareRoot => "square root of negative number", }), Ok(value) => println!("{}", value), } } } fn main() { checked::op(1.0, 10.0); }
ドキュメンテーションを参照し、Resultを
操作するたくさんのメソッドを探してみてください。
panic!
panic!マクロでパニックを生成し、スタックを巻き戻すことができます。
スタックを巻き戻すときは、スレッド内の全てのリソースのデストラクタを
呼び出してメモリを開放します。
プログラムを1スレッドで管理しているので、panic!はパニックメッセージ
を表示して終了します。
// 整数除算(/)の再実装 fn division(dividend: i32, divisor: i32) -> i32 { if divisor == 0 { // 0で割るとパニックする panic!("division by zero"); } else { dividend / divisor } } // `main`タスク fn main() { // ヒープに整数を確保する let _x = Box::new(0i32); // この操作は失敗する。 division(3, 0); println!("This point won't be reached!"); // ここにはたどり着きません! // `_x`はここで開放されるはずです。 }
panic!がメモリリークを起こしていないことを確認しましょう。
$ rustc panic.rs && valgrind ./panic
==4401== Memcheck, a memory error detector
==4401== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==4401== Using Valgrind-3.10.0.SVN and LibVEX; rerun with -h for copyright info
==4401== Command: ./panic
==4401==
thread '<main>' panicked at 'division by zero', panic.rs:5
==4401==
==4401== HEAP SUMMARY:
==4401== in use at exit: 0 bytes in 0 blocks
==4401== total heap usage: 18 allocs, 18 frees, 1,648 bytes allocated
==4401==
==4401== All heap blocks were freed -- no leaks are possible
==4401==
==4401== For counts of detected and suppressed errors, rerun with: -v
==4401== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
ハッシュマップ
ベクターは整数でインデックスを管理しましたが、HashMapはキーで値を保持します。
HashMapキーは、真偽値、整数、文字列など、EqトレイトとHashトレイトを実装する
すべての型をキーにできます。これについては次の節で詳しく扱います。
ベクターのように、HashMapは拡張でき、さらに余分なスペースを縮小できます。
容量を指定してHashMap::with_capacity(uint)とするか、HashMap::new()で
デフォルト容量でHashMapを初期化する(推奨)ことで、HashMapを作ることができます。
use std::collections::HashMap; fn call(number: &str) -> &str { match number { "798-1364" => "We're sorry, the call cannot be completed as dialed. Please hang up and try again.", // ただいま電話に出ることができません。しばらくしてもう一度おかけください。 "645-7689" => "Hello, this is Mr. Awesome's Pizza. My name is Fred. What can I get for you today?", // こんにちは。Mr. Awesome's PizzaのFredです。何をご注文されますか? _ => "Hi! Who is this again?" // もしもし、誰ですか? } } fn main() { let mut contacts = HashMap::new(); contacts.insert("Daniel", "798-1364"); contacts.insert("Ashley", "645-7689"); contacts.insert("Katie", "435-8291"); contacts.insert("Robert", "956-1745"); // 参照をとり、Option<&V>を返す match contacts.get(&"Daniel") { Some(&number) => println!("Calling Daniel: {}", call(number)), _ => println!("Don't have Daniel's number."), // Danielの番号を持っていません } // `HashMap::insert()`は`None`を返す // if the inserted value is new, `Some(value)` otherwise contacts.insert("Daniel", "164-6743"); match contacts.get(&"Ashley") { Some(&number) => println!("Calling Ashley: {}", call(number)), _ => println!("Don't have Ashley's number."), } contacts.remove(&"Ashley"); // `HashMap::iter()`はイテレータを返します。 // (&'a key, &'a value)は順番になる。 for (contact, &number) in contacts.iter() { println!("Calling {}: {}", contact, call(number)); } }
ハッシュやハッシュマップ(ハッシュテーブルとも呼ばれます) についての詳しい情報は、Hash Table Wikipedia を参照してください。
独自のキーの型
EqトレイトとHashトレイトを実装しているすべての型はHashMapの
キーとして使用できます。
bool(値を2つしか保持できないので使えないと思います)int、uintなどのすべての整数型Stringや&str(StringをキーとするHashMapで.get()を 呼び出すと&strが帰ってきます。
f32とf64は浮動小数点の精度エラー
が出る可能性があるため、Hashを実装していません。
そのため、hashmapには使用できません。
すべてのコレクションはEqとHashを実装している場合があります、例えば、
TがHashを実装している時、Vec<T>にもHashが実装されます。
この一行で簡単にカスタム型にEqとHashを実装することができます。
#[derive(PartialEq, Eq, Hash)]
後はコンパイラが勝手にやってくれます。もっと詳細に制御したい場合は、
自分でEqやHashを実装することもできます。このガイドではHashの
実装方法については触れません。
structをHashMapに対して使い、シンプルなユーザーログオン
システムを作ってみましょう。
use std::collections::HashMap; // Eqを継承するには、PartialEqが実装されている必要があります。 #[derive(PartialEq, Eq, Hash)] struct Account<'a>{ username: &'a str, password: &'a str, } struct AccountInfo<'a>{ name: &'a str, email: &'a str, } type Accounts<'a> = HashMap<Account<'a>, AccountInfo<'a>>; fn try_logon<'a>(accounts: &Accounts<'a>, username: &'a str, password: &'a str){ println!("Username: {}", username); println!("Password: {}", password); println!("Attempting logon..."); let logon = Account { username, password, }; match accounts.get(&logon) { Some(account_info) => { println!("Successful logon!"); println!("Name: {}", account_info.name); println!("Email: {}", account_info.email); }, _ => println!("Login failed!"), } } fn main(){ let mut accounts: Accounts = HashMap::new(); let account = Account { username: "j.everyman", password: "password123", }; let account_info = AccountInfo { name: "John Everyman", email: "j.everyman@email.com", }; accounts.insert(account, account_info); try_logon(&accounts, "j.everyman", "psasword123"); try_logon(&accounts, "j.everyman", "password123"); }
ハッシュセット
HashSetは、キーしか持たないHashMapで、HashSet<T>は、実際には
HashMap<T, ()>のラップです。
「何が特殊なの?」「キーをVecに入れたら良いじゃないか。」と思うかもしれません。
HashSet特有の機能は、重複した要素を持たないことが保証されていることです。
これが他のコレクションとの大きな違いです。HashSetはその一つの実装にしか
過ぎません。(こちらも参照: BTreeSet)
すでに存在する値をHashSetに挿入した場合はどうなるのでしょうか。(これは同じ
ハッシュを持つ新しい値を代入するのと同じなので)このとき、新しい値が古い値を
置き換えます。
これは2回以上同じことをしたくないときや、すでに知っているものをもう一度取得したくない 場合に役立ちます。
HashSetはさらに多くのことができます。
HashSetは以下の4つの操作を持っています(すべてイテレータを返します。)
-
union: 2つの集合から重複しない要素を取り出す。 -
difference: 最初の集合にあって、2つ目にない要素をすべて取り出す。 -
intersection: 両方の集合にある要素を取り出す。 -
symmetric_difference: 2つの集合の内一つにはあるが、両方には無いものをすべて取り出す。
以下の例で全て試してみましょう:
use std::collections::HashSet; fn main() { let mut a: HashSet<i32> = vec![1i32, 2, 3].into_iter().collect(); let mut b: HashSet<i32> = vec![2i32, 3, 4].into_iter().collect(); assert!(a.insert(4)); assert!(a.contains(&4)); // `HashSet::insert()`はすでに値が存在する時 // falseを返す。 assert!(b.insert(4), "Value 4 is already in set B!"); // FIXME ^ この行をコメントアウトしてください。 b.insert(5); // 要素の型が`Debug`を実装していれば、 // そのコレクションも`Debug`を実装する。 // 普通`[elem1, elem2, ...]`のように出力される。 println!("A: {:?}", a); println!("B: {:?}", b); // [1, 2, 3, 4, 5]を順番に出力する。 println!("Union: {:?}", a.union(&b).collect::<Vec<&i32>>()); // これは[1]を出力する println!("Difference: {:?}", a.difference(&b).collect::<Vec<&i32>>()); // これは[2, 3, 4]を順番に出力する。 println!("Intersection: {:?}", a.intersection(&b).collect::<Vec<&i32>>()); // [1, 5]を出力する。 println!("Symmetric Difference: {:?}", a.symmetric_difference(&b).collect::<Vec<&i32>>()); }
(サンプルはHashSetのドキュメントから拝借しました。)
Rc
複数のの所有権が必要な場合、Rc(Reference Counting、参照カウント)が使えます。Rcは内部に保持している値がいくつの場所から使用されているのかを記録しています。
Rcが複製された時カウントが1増え、Rcがスコープの外にdropされた時、カウントが1減ります。
カウントが0になった時、所有権は残っていないことを意味するため、値が開放されます。
Rcを複製してもコピーは作りません。複製したらもう一つのポインタを作り、カウントを増やすだけです。
use std::rc::Rc; fn main() { let rc_examples = "Rc examples".to_string(); { println!("--- rc_a is created ---"); let rc_a: Rc<String> = Rc::new(rc_examples); println!("Reference Count of rc_a: {}", Rc::strong_count(&rc_a)); { println!("--- rc_a is cloned to rc_b ---"); let rc_b: Rc<String> = Rc::clone(&rc_a); println!("Reference Count of rc_b: {}", Rc::strong_count(&rc_b)); println!("Reference Count of rc_a: {}", Rc::strong_count(&rc_a)); // `Rc`中の値が等しい時、Rcは等しいです。 println!("rc_a and rc_b are equal: {}", rc_a.eq(&rc_b)); // 値のメソッドを直接呼び出すことができます。 println!("Length of the value inside rc_a: {}", rc_a.len()); println!("Value of rc_b: {}", rc_b); println!("--- rc_b is dropped out of scope ---"); } println!("Reference Count of rc_a: {}", Rc::strong_count(&rc_a)); println!("--- rc_a is dropped out of scope ---"); } // エラー! `rc_examples`はすでに`rc_a`にムーブされています。 // `rc_a`がドロップされた時、`rc_examples`もドロップされます。 // println!("rc_examples: {}", rc_examples); // TODO ^ この行をアンコメントしてみてください。 }
こちらも参照:
その他のStd
標準ライブラリは、このような用途のためのその他の 型を提供しています。
- スレッド
- チャンネル
- ファイル I/O
これらは基本型を拡張することで提供されています。
こちらも参照:
スレッド
Rustはspawn関数でクロージャを引数にとり、OSのスレッドを生成する
メカニズムを備えています。
use std::thread; static NTHREADS: i32 = 10; // This is the `main` thread fn main() { // Make a vector to hold the children which are spawned. let mut children = vec![]; for i in 0..NTHREADS { // Spin up another thread children.push(thread::spawn(move || { println!("this is thread number {}", i); })); } for child in children { // Wait for the thread to finish. Returns a result. let _ = child.join(); } }
これらのスレッドはOSによってスケジューリングされています。
テストケース: map-reduce
Rustを使えば、苦労なしに簡単に並行プログラミングできます。
標準ライブラリはスレッド管理のための素晴らしい機能を提供しています。 これらは、Rustの所有権やエイリアスの概念と協調した、データ競合の少ない 並行化を可能にします。
エイリアシングルール(一つの他と共存できない可変参照と、他といくつでも共存できる不変参照)によって、
他のスレッドとの競合を原理的になくすことができます。(同期したいときは、そのためのMutexやChannnel
といった型が存在します。)
この例では、ブロック内のすべての数字を加算します。ここでは、ブロックをチャンクに分割して、それぞれの チャンクの計算を別のスレッドで行っています。それぞれのスレッドが合計を計算したら、即座にそれぞれの スレッドの持つ数を合算します。
スレッドを超えてデータを参照しても、コンパイラは読み取り専用の参照しか渡していないことを知っているので、
安全でない操作やデータ競合が起こらないことに注意してください。また、データをスレッドにmoveしても、
スレッドが終了するまでデータを確保するので、危険なポインタもできません。
use std::thread; // `main`スレッド fn main() { // これが処理するデータです。 // map-reduceアルゴリズムでこれらの数字の合計を計算します。 // 空白によってチャンクを分割できます。 // // TODO: 空白を増やしたらどうなるか見てみましょう! let data = "86967897737416471853297327050364959 11861322575564723963297542624962850 70856234701860851907960690014725639 38397966707106094172783238747669219 52380795257888236525459303330302837 58495327135744041048897885734297812 69920216438980873548808413720956532 16278424637452589860345374828574668"; // 子スレッドを保持するベクタ let mut children = vec![]; /************************************************************************* * "Map"フェーズ * * データを分割し、最初の処理を行う ************************************************************************/ // それぞれの計算のためにデータを分割する。 // それぞれのチャンクは(&str)から本物のデータを参照できる。 let chunked_data = data.split_whitespace(); // データ単位ごとにイテレーションする // .enumerate()は現在のループインデックスとデータを // タプル"(index, element)"に入れ、即座に2つの変数 // "i"と"data_segment"に「分割代入」する。 for (i, data_segment) in chunked_data.enumerate() { println!("data segment {} is \"{}\"", i, data_segment); // それぞれのデータ単位を別々のスレッドで処理する。 // // spawn()はスレッドの情報を返し、これは返り値にアクセス // するために保持しないといけない。 // // 'move || -> u32'は、 // * 引数を取らない ('||') // * キャプチャした変数を移動するする('move') // * 32ビット浮動小数点整数を返す('-> u32') // クロージャです。 // // Rustは、クロージャ自体から'-> u32'を推論できるので // 省略することもできる。 // // TODO: 'move'を外すとどうなるか見てみましょう。 children.push(thread::spawn(move || -> u32 { // そのデータ単位の合計を計算する let result = data_segment // 文字ごとにイテレートする.. .chars() // ..文字を数値に変換する.. .map(|c| c.to_digit(10).expect("should be a digit")) // ..すべての数値を合計する .sum(); // println!はstdoutをロックするので、テキストの競合は起こらない println!("processed segment {}, result={}", i, result); // は"式ベース言語"なので、"return"は必要なく、ブロックの最後の式が // 自動的に返される。 result })); } /************************************************************************* * "Reduce"フェーズ * * それぞれの結果を取得し、合算する。 ************************************************************************/ // 新しいベクターにそれぞれの結果を入れる。 let mut intermediate_sums = vec![]; for child in children { // それぞれの子スレッドの返り値を取得する。 let intermediate_sum = child.join().unwrap(); intermediate_sums.push(intermediate_sum); } // すべての数値を合算して、最後の結果とする。 // // "ターボフィッシュ"(::<>)を使うことでsum()に型のヒントを与える。 // // TODO: ターボフィッシュを使わず、明示的に // final_resultの型を指定してください。 let final_result = intermediate_sums.iter().sum::<u32>(); println!("Final sum result: {}", final_result); }
代入
ユーザー入力からスレッド数を決めるのは賢くありません。ユーザーがもしたくさんのスペースを入れたとして、 本当に2,000個のスレッドを作る必要があるでしょうか? いつも限られた数のチャンクを作るようにして、 その数を静的定数としてプログラムのはじめに定義するようにプログラムを変更してみてください。
こちらも参照:
- スレッド
- ベクター
- イテレータ
- クロージャ
- move
- クロージャの
move - 分割代入
- 型インターフェースを補助するターボフィッシュ
- unwrapとexpect
- enumerate
チャンネル
Rustはスレッド間で同期するためのチャンネルを提供しています。チャンネルを
使えば、情報を2つのスレッド(SenderとReceiver)間で一方的に送信できます。
use std::sync::mpsc::{Sender, Receiver}; use std::sync::mpsc; use std::thread; static NTHREADS: i32 = 3; fn main() { // チャンネルは`Sender<T>`と`Receiver<T>`という2つの地点を持っていて、 // `T`は送信する情報の型です。 // (型注釈は必要ないものです) let (tx, rx): (Sender<i32>, Receiver<i32>) = mpsc::channel(); let mut children = Vec::new(); for id in 0..NTHREADS { // 送り手(Sender)はコピーできます。 let thread_tx = tx.clone(); // それぞれのスレッドがチャンネルを使ってデータを送信します。 let child = thread::spawn(move || { // このスレッドは`thread_tx`の所有権を必要とします。 // それぞれのスレッドがキューにデータを送ります。 thread_tx.send(id).unwrap(); // 送ることは制限された操作ではないので、スレッドは // 即座に次の操作ができます。 println!("thread {} finished", id); }); children.push(child); } // ここにすべてのメッセージが溜まっています let mut ids = Vec::with_capacity(NTHREADS as usize); for _ in 0..NTHREADS { // `recv`メソッドはチャンネルからのメッセージを一つとります。 // `recv`はメッセージが届いていないときに操作がブロックされます。 ids.push(rx.recv()); } // 残りの作業を終えるため、スレッドの終了を待つ。 for child in children { child.join().expect("oops! the child thread panicked"); } // メッセージを贈られた順に並べる。 println!("{:?}", ids); }
パス
Path構造体はファイルシステム下のファイルパスを格納します。これにはUNIX系
システム用のposix::Pathと、Windows用のwindows::Pathの2つがあります。
これらは適切なプラットフォーム依存のPath列挙子を提供します。
PathはOsStrから作ることができ、ファイルやディレクトリに対する情報
を操作するいくつかのメソッドを持っています。
Pathは内部的にはUTF-8文字列として保存されず、バイト列(Vec<u8>)として
保存されることに注意してください。そのため、Pathから&strへの変換は
失敗する可能性があります(Optionが返されます)。
use std::path::Path; fn main() { // `&'static str`から`Path`を作る let path = Path::new("."); // `display`メソッドは`Show`できる構造体を返します。 let _display = path.display(); // `join`はパスをOS依存のセパレータで結合し、 // 新しいパスを返します。 let new_path = path.join("a").join("b"); // パスを文字列スライスに変換する。 match new_path.to_str() { None => panic!("new path is not a valid UTF-8 sequence"), Some(s) => println!("new path is {}", s), } }
他のPath(posix::Pathまたはwindows::Path)のメソッドや
Metadata構造体についても確認してください。
こちらも参照:
ファイルI/O
File構造体はすでに開いたファイルを表します。(これはファイル記述子の
ラップです。) また、これに対して読み取りや書き込みの権限を要求できます。
ファイルI/Oには多くの失敗要素があるため, Fileのメソッドは
Result<T, io::Error>のエイリアスであるio::Result<T>を返します。
これによって、ファイルI/Oのエラーを明確にできます。これのおかげで、 パスの失敗について、自身を持って処理できます。
open
open静的メソッドはファイルを読み取り専用モードで開くのに使うことができます。
Fileはリソース、ファイル記述子を所有していて、いつファイルがdropされるかに
ついての配慮をしています。
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
fn main() {
// 開くファイルのパスを作る
let path = Path::new("hello.txt");
let display = path.display();
// 読み取り専用でパスを開き、`io::Result<File>`を返す。
let mut file = match File::open(&path) {
Err(why) => panic!("couldn't open {}: {}", display, why),
Ok(file) => file,
};
// ファイルを文字列に読み込み、`io::Result<usize>`を返す。
let mut s = String::new();
match file.read_to_string(&mut s) {
Err(why) => panic!("couldn't read {}: {}", display, why),
Ok(_) => print!("{} contains:\n{}", display, s),
}
// `file`がスコープを出たことで、"hello.txt"は閉じられます。
}
これが成功時の出力です。
$ echo "Hello World!" > hello.txt
$ rustc open.rs && ./open
hello.txt contains:
Hello World!
(hello.txtが存在しない、またはhello.txtが読み込めないなどの
場合、このプログラムはエラーします。)
create
createはファイルを書き込み専用で開きます。もしファイルがすでに存在する場合、
古いものは破棄され、存在しないときは新しく作成されます。
static LOREM_IPSUM: &str =
"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
";
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
fn main() {
let path = Path::new("lorem_ipsum.txt");
let display = path.display();
// 書き込み専用でファイルを開き、`io::Result<File>`を返します。
let mut file = match File::create(&path) {
Err(why) => panic!("couldn't create {}: {}", display, why),
Ok(file) => file,
};
// `LOREM_IPSUM`文字列を`file`に書き込み、`io::Result<()>`を返します。
match file.write_all(LOREM_IPSUM.as_bytes()) {
Err(why) => panic!("couldn't write to {}: {}", display, why),
Ok(_) => println!("successfully wrote to {}", display),
}
}
成功時の出力は以下の通りです。
$ rustc create.rs && ./create
successfully wrote to lorem_ipsum.txt
$ cat lorem_ipsum.txt
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
(前の例のように、状況によっては失敗することがあります。)
OpenOptions構造体でファイルをどのように開くか決めることができます。
read_lines
lines()メソッドはファイルを1行ずつ読み込むイテレータ
を返します。
File::openはAsRef<Path>を期待しますが、
read_lines()は入力を期待します。
use std::fs::File; use std::io::{self, BufRead}; use std::path::Path; fn main() { // ファイルが存在し、読み込めることが前提 if let Ok(lines) = read_lines("./hosts") { // イテレータを消費し、文字列の`Result`を返す for line in lines { if let Ok(ip) = line { println!("{}", ip); } } } } // エラーに遭遇しても良いように、Resultを返す。 // BufReaderを行ごとに読み取るイテレータを返す。 fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>> where P: AsRef<Path>, { let file = File::open(filename)?; Ok(io::BufReader::new(file).lines()) }
このプログラムを実行すると、シンプルにすべての行の値が得られる。
$ echo -e "127.0.0.1\n192.168.0.1\n" > hosts
$ rustc read_lines.rs && ./read_lines
127.0.0.1
192.168.0.1
この方法は、特に大きなファイルを扱う時、Stringをすべてメモリに
保存するより効率的です。
子プロセス
process::Output構造体は終了した子プロセスの出力を表します。
process::Command構造体はプロセスを作成します。
use std::process::Command;
fn main() {
let output = Command::new("rustc")
.arg("--version")
.output().unwrap_or_else(|e| {
panic!("failed to execute process: {}", e)
});
if output.status.success() {
let s = String::from_utf8_lossy(&output.stdout);
print!("rustc succeeded and stdout was:\n{}", s);
} else {
let s = String::from_utf8_lossy(&output.stderr);
print!("rustc failed and stderr was:\n{}", s);
}
}
(間違ったフラグをrustcに渡すとエラーします。)
パイプ
std::Child構造体は実行中の子プロセスを表し、stdin、stdoutやstderr
を制御してプロセスをパイプしてつなぐことができます。
use std::io::prelude::*;
use std::process::{Command, Stdio};
static PANGRAM: &'static str =
"the quick brown fox jumped over the lazy dog\n";
fn main() {
// `wc`コマンドを実行する。
let process = match Command::new("wc")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn() {
Err(why) => panic!("couldn't spawn wc: {}", why),
Ok(process) => process,
};
// `wc`の`stdin`を書き込む。
//
// `stdin`は`Option<ChildStdin>`型ですが、インスタンスが存在することが
// 分かるため、`unwrap`できます。
match process.stdin.unwrap().write_all(PANGRAM.as_bytes()) {
Err(why) => panic!("couldn't write to wc stdin: {}", why),
Ok(_) => println!("sent pangram to wc"),
}
// `stdin`は上の呼び出しの後、使われないため`drop`されます。
// そしてパイプを閉じます。
//
// これをしないと、 入力を送っただけなので、`wc`は
// 処理を開始します。
// `stdout`も`Option<ChildStdout>`型なので、unwrapしないといけません。
let mut s = String::new();
match process.stdout.unwrap().read_to_string(&mut s) {
Err(why) => panic!("couldn't read wc stdout: {}", why),
Ok(_) => print!("wc responded with:\n{}", s),
}
}
ウェイト
process::Childは子プロセスが終了するのを待つことができます。
process::ExitStatusを返すChild::wait()を呼び出してください。
use std::process::Command;
fn main() {
let mut child = Command::new("sleep").arg("5").spawn().unwrap();
let _result = child.wait().unwrap();
println!("reached end of main");
}
$ rustc wait.rs && ./wait
# `wait`は`sleep 5`コマンドが終了するまで5秒待ちます。
reached end of main
ファイルシステム制御
std::fsモジュールはファイルシステム制御のためのいくつかの関数を備えています。
use std::fs;
use std::fs::{File, OpenOptions};
use std::io;
use std::io::prelude::*;
use std::os::unix;
use std::path::Path;
// `% cat path`のシンプルな実装
fn cat(path: &Path) -> io::Result<String> {
let mut f = File::open(path)?;
let mut s = String::new();
match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(e) => Err(e),
}
}
// `% echo s > path`のシンプルな実装
fn echo(s: &str, path: &Path) -> io::Result<()> {
let mut f = File::create(path)?;
f.write_all(s.as_bytes())
}
// `% touch path`(すでにあるファイルは無視)のシンプルな実装
fn touch(path: &Path) -> io::Result<()> {
match OpenOptions::new().create(true).write(true).open(path) {
Ok(_) => Ok(()),
Err(e) => Err(e),
}
}
fn main() {
println!("`mkdir a`");
// ディレクトリを作成し、`io::Result<()>`を返す。
match fs::create_dir("a") {
Err(why) => println!("! {:?}", why.kind()),
Ok(_) => {},
}
println!("`echo hello > a/b.txt`");
// 以下のmatchは`unwrap_or_else`メソッドで簡単に実装できます。
echo("hello", &Path::new("a/b.txt")).unwrap_or_else(|why| {
println!("! {:?}", why.kind());
});
println!("`mkdir -p a/c/d`");
// 再帰的にディレクトリを作成し、`io::Result<()>`を返す。
fs::create_dir_all("a/c/d").unwrap_or_else(|why| {
println!("! {:?}", why.kind());
});
println!("`touch a/c/e.txt`");
touch(&Path::new("a/c/e.txt")).unwrap_or_else(|why| {
println!("! {:?}", why.kind());
});
println!("`ln -s ../b.txt a/c/b.txt`");
// シンボリックリンクを作成し、`io::Result<()>`を返す。
if cfg!(target_family = "unix") {
unix::fs::symlink("../b.txt", "a/c/b.txt").unwrap_or_else(|why| {
println!("! {:?}", why.kind());
});
}
println!("`cat a/c/b.txt`");
match cat(&Path::new("a/c/b.txt")) {
Err(why) => println!("! {:?}", why.kind()),
Ok(s) => println!("> {}", s),
}
println!("`ls a`");
// Read the contents of a directory, returns `io::Result<Vec<Path>>`
match fs::read_dir("a") {
Err(why) => println!("! {:?}", why.kind()),
Ok(paths) => for path in paths {
println!("> {:?}", path.unwrap().path());
},
}
println!("`rm a/c/e.txt`");
// ファイルを削除し、`io::Result<()>`を返す。
fs::remove_file("a/c/e.txt").unwrap_or_else(|why| {
println!("! {:?}", why.kind());
});
println!("`rmdir a/c/d`");
// 空のディレクトリを削除し、`io::Result<()>`を返す。
fs::remove_dir("a/c/d").unwrap_or_else(|why| {
println!("! {:?}", why.kind());
});
}
これは成功した場合の出力です。
$ rustc fs.rs && ./fs
`mkdir a`
`echo hello > a/b.txt`
`mkdir -p a/c/d`
`touch a/c/e.txt`
`ln -s ../b.txt a/c/b.txt`
`cat a/c/b.txt`
> hello
`ls a`
> "a/b.txt"
> "a/c"
`rm a/c/e.txt`
`rmdir a/c/d`
最終的なaディレクトリの構成は:
$ tree a
a
|-- b.txt
`-- c
`-- b.txt -> ../b.txt
1 directory, 2 files
catは?を使っても実装できます。
fn cat(path: &Path) -> io::Result<String> {
let mut f = File::open(path)?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
こちらも参照:
プログラム引数
標準ライブラリ
標準ライブラリはStringの各引数に対するイテレータを返すstd::env::args
によってアクセスできます。
use std::env; fn main() { let args: Vec<String> = env::args().collect(); // 最初の引数はプログラムの呼び出しに使われるものです。 println!("My path is {}.", args[0]); // その他の引数はプログラムに与えられるオプションです。 // このようにプログラムを呼び出します。 // $ ./args arg1 arg2 println!("I got {:?} arguments: {:?}.", args.len() - 1, &args[1..]); }
$ ./args 1 2 3
My path is ./args.
I got 3 arguments: ["1", "2", "3"].
クレート
その他にも、コマンドラインアプリケーションを作成するのに役立つ多くのクレートがあります。
Rust Cookbookには、最もポピュラーなコマンドライン解析ライブラリclapの使い方が書かれています。
引数解析
シンプルな引数解析にはmacthを使います。
use std::env; fn increase(number: i32) { println!("{}", number + 1); } fn decrease(number: i32) { println!("{}", number - 1); } // 使い方: // match_args <string> // 与えられた文字列が正しい答えかどうか確認します。 // match_args {{increase|decrease}} <integer> // 与えられた整数をインクリメントまたはデクリメントします。 fn help() { println!("usage: match_args <string> Check whether given string is the answer. match_args {{increase|decrease}} <integer> Increase or decrease given integer by one."); } fn main() { let args: Vec<String> = env::args().collect(); match args.len() { // 引数が渡されなかった場合 1 => { println!("My name is 'match_args'. Try passing some arguments!"); // 私は"match_args"です。なにか引数を渡してみてください! }, // 1つ引数が渡された場合 2 => { match args[1].parse() { Ok(42) => println!("This is the answer!"), _ => println!("This is not the answer."), } }, // 1つのコマンドと1つの引数が渡された場合 3 => { let cmd = &args[1]; let num = &args[2]; // 数値を解析する let number: i32 = match num.parse() { Ok(n) => { n }, Err(_) => { eprintln!("error: second argument not an integer"); help(); return; }, }; // コマンドを解析する match &cmd[..] { "increase" => increase(number), "decrease" => decrease(number), _ => { eprintln!("error: invalid command"); help(); }, } }, // その他の場合 _ => { // show a help message help(); } } }
$ ./match_args Rust
This is not the answer.
$ ./match_args 42
This is the answer!
$ ./match_args do something
error: second argument not an integer
usage:
match_args <string>
Check whether given string is the answer.
match_args {increase|decrease} <integer>
Increase or decrease given integer by one.
$ ./match_args do 42
error: invalid command
usage:
match_args <string>
Check whether given string is the answer.
match_args {increase|decrease} <integer>
Increase or decrease given integer by one.
$ ./match_args increase 42
43
外部関数インターフェース
RustはC言語のライブラリの外部関数インターフェース(FFI)を提供しています。外部の
関数はexternブロックで宣言され、そのブロックにはライブラリ名を付けた#[link]
属性が与えられている必要があります。
use std::fmt;
// このブロックはlibmとリンクします。
#[link(name = "m")]
extern {
// これは外部関数です。
// これは単精度複素数の平方根を求めます。
fn csqrtf(z: Complex) -> Complex;
fn ccosf(z: Complex) -> Complex;
}
// 外部関数は安全でない可能性があるので、安全なラッパー
// 関数が必要です。
fn cos(z: Complex) -> Complex {
unsafe { ccosf(z) }
}
fn main() {
// z = -1 + 0i
let z = Complex { re: -1., im: 0. };
// 外部関数の呼び出しはunsafeです。
let z_sqrt = unsafe { csqrtf(z) };
println!("the square root of {:?} is {:?}", z, z_sqrt);
// unsafeをラップした安全なAPIを呼び出す。
println!("cos({:?}) = {:?}", z, cos(z));
}
// 単精度複素数の最小実装
#[repr(C)]
#[derive(Clone, Copy)]
struct Complex {
re: f32,
im: f32,
}
impl fmt::Debug for Complex {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.im < 0. {
write!(f, "{}-{}i", self.re, -self.im)
} else {
write!(f, "{}+{}i", self.re, self.im)
}
}
}
テスト
Rustはプログラムの正確性に重点をおくプログラミング言語であるため、 言語自体にテストを書く仕組みを備えています。
テストはこの3つのスタイルからなります:
- ユニットテスト
- ドキュメンテーションテスト
- 整合性テスト
さらに、テストのための依存を管理する仕組みも備えています:
こちらも参照
- The Bookのテストの章
- API Guidelinesのdoc-testの章
ユニットテスト
テストはコードが期待した動作をすることを確かめるためのRustの機能です。テスト関数は 典型的にはいくつかのセットアップをして、テストしたいコードを書き、期待したものと 合致しているか確かめます。
ユニットテストの多くは#[cfg(test)]属性のついたtestsモジュール
に入っていて、テスト関数には#[test]属性がついています。
テストは、関数がpanicしたときに失敗し、そのためのマクロがいくつかあります。
assert!(expression)- 式がfalseならパニックするassert_eq!(left, right)とassert_ne!(left, right)- 右と左の式が それぞれ違うとき、同じときにパニックする
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
// これは失敗する例なので、間違った加算関数になっています。
#[allow(dead_code)]
fn bad_add(a: i32, b: i32) -> i32 {
a - b
}
#[cfg(test)]
mod tests {
// これでモジュールの外側の関数をすべてスコープに導入できます。
use super::*;
#[test]
fn test_add() {
assert_eq!(add(1, 2), 3);
}
#[test]
fn test_bad_add() {
// この確認は失敗します。
// プライベート関数もテストできることを知っておいてください!
assert_eq!(bad_add(1, 2), 3);
}
}
cargo testでテストを走らせることができます。
$ cargo test
running 2 tests
test tests::test_bad_add ... FAILED
test tests::test_add ... ok
failures:
---- tests::test_bad_add stdout ----
thread 'tests::test_bad_add' panicked at 'assertion failed: `(left == right)`
left: `-1`,
right: `3`', src/lib.rs:21:8
note: Run with `RUST_BACKTRACE=1` for a backtrace.
failures:
tests::test_bad_add
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
テストと?
このテストは返り値を持ちませんでした。しかし、Rust 2018では、ユニットテストは
?を使うためのResult<()>を返すことができます。これを使えば、簡潔にテストが
書けます。
fn sqrt(number: f64) -> Result<f64, String> { if number >= 0.0 { Ok(number.powf(0.5)) } else { Err("negative floats don't have square roots".to_owned()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_sqrt() -> Result<(), String> { let x = 4.0; assert_eq!(sqrt(x)?.powf(2.0), x); Ok(()) } }
「The Edition Guide」に詳細があります。
テストとパニック
パニックするべき処理をテストするときは、#[should_panic]属性を使ってください。この属性は
オプションで引数expected = とパニックしたときのメッセージを渡すことができます。もし関数が
複数のパニックをするときは、ここで正しいパニックを指定することを忘れないでください。
pub fn divide_non_zero_result(a: u32, b: u32) -> u32 {
if b == 0 {
panic!("Divide-by-zero error");
} else if a < b {
panic!("Divide result is zero");
}
a / b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_divide() {
assert_eq!(divide_non_zero_result(10, 2), 5);
}
#[test]
#[should_panic]
fn test_any_panic() {
divide_non_zero_result(1, 0);
}
#[test]
#[should_panic(expected = "Divide result is zero")]
fn test_specific_panic() {
divide_non_zero_result(1, 10);
}
}
これらのテストを実行すると:
$ cargo test
running 3 tests
test tests::test_any_panic ... ok
test tests::test_divide ... ok
test tests::test_specific_panic ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Doc-tests tmp-test-should-panic
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
特定のテストを実行する
cargo testコマンドにテストの名前を渡すことで、特定のテストを実行できます。
$ cargo test test_any_panic
running 1 test
test tests::test_any_panic ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out
Doc-tests tmp-test-should-panic
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
名前の一部を指定すると、それにマッチするすべてのテストが実行できます。
$ cargo test panic
running 2 tests
test tests::test_any_panic ... ok
test tests::test_specific_panic ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out
Doc-tests tmp-test-should-panic
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
テストを無視する
テストに#[ignore]属性を付けることでテストを無視することができます。そして、
cargo test -- --ignoredコマンドで実行することができます。
#![allow(unused_variables)] fn main() { pub fn add(a: i32, b: i32) -> i32 { a + b } #[cfg(test)] mod tests { use super::*; #[test] fn test_add() { assert_eq!(add(2, 2), 4); } #[test] fn test_add_hundred() { assert_eq!(add(100, 2), 102); assert_eq!(add(2, 100), 102); } #[test] #[ignore] fn ignored_test() { assert_eq!(add(0, 0), 0); } } }
$ cargo test
running 3 tests
test tests::ignored_test ... ignored
test tests::test_add ... ok
test tests::test_add_hundred ... ok
test result: ok. 2 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out
Doc-tests tmp-ignore
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
$ cargo test -- --ignored
running 1 test
test tests::ignored_test ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Doc-tests tmp-ignore
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
ドキュメンテーションテスト
Rustプロジェクトのドキュメントを作る簡単な方法は、ソースに注釈を付けることです。 コメントドキュメントはmarkdownで書くことができ、コードブロックをサポートしています。 また、コードブロックをテストとして扱うことができます。
/// 最初の行は関数の簡単な説明です。
///
/// 次の行から詳しい説明が始まります。コードブロックは
/// 3つのバッククオートや`fn main()`、`extern crate <cratename>`
/// などで始まり、`doccomments`クレートで実行されることが想定されています。
///
/// ```
/// let result = doccomments::add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
/// 普通ドキュメンテーションコメントは"Examples"(例)、"Panics"(パニック)、"Failures"(失敗)
/// のような章を持ちます
///
/// 次の関数で2つの関数を割ります。
///
/// # Examples
///
/// ```
/// let result = doccomments::div(10, 2);
/// assert_eq!(result, 5);
/// ```
///
/// # Panics
///
/// この関数は第2引数が0のときにパニックします。
///
/// ```rust,should_panic
/// // panics on division by zero
/// doccomments::div(10, 0);
/// ```
pub fn div(a: i32, b: i32) -> i32 {
if b == 0 {
panic!("Divide-by-zero error");
}
a / b
}
テストはcargo testで実行できます。
$ cargo test
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Doc-tests doccomments
running 3 tests
test src/lib.rs - add (line 7) ... ok
test src/lib.rs - div (line 21) ... ok
test src/lib.rs - div (line 31) ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
ドキュメンテーションテストのモチベーション
ドキュメンテーションテストの主な目的は機能を練習するための例を提供することであり、
これは重要なガイドラインです。これによりドキュメント
のコード片が使用できるようになります。しかし、main関数はunitを返すため、
?を使うとコンパイルエラーが起きます。ソースの行を隠すことでこれを防げます。
例えば、fn try_main() -> Result<(), ErrorType>を書いたとしても、これを隠し、
さらにmain内のunwrapも隠すことができます。複雑に聞こえますか? これが例です。
/// ドキュメンテーションテストで使う`try_main`を隠す
///
/// ```
/// # // `#`で行を隠すことができますが、これはまだコンパイルできます!
/// # fn try_main() -> Result<(), String> { // ドキュメントで示す行をラップする
/// let res = try::try_div(10, 2)?;
/// # Ok(()) // try_mainから値を返す
/// # }
/// # fn main() { // unwrap()するmain関数を書く。
/// # try_main().unwrap(); // try_mainを呼び、unwrapする
/// # // これでテストが失敗したときにパニックする
/// # }
/// ```
pub fn try_div(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err(String::from("Divide-by-zero"))
} else {
Ok(a / b)
}
}
こちらも参照
- RFC505のドキュメンテーションスタイル
- API Guidelinesのドキュメンテーションガイドライン
整合性テスト
ユニットテストは独立した一つのモジュール上で実行されます。これはプライベート なコードをテストするには必要です。整合性テストはクレートの外にあり、他のコードがそうで あるように、パブリックなインターフェースだけにアクセスできます。これはライブラリの いろいろな部分が適切に動くかテストするのに使います。
Cargoは整合性テストをsrcの隣のtestsディレクトリに置くことを想定しています。
ファイルsrc/lib.rs:
// このクレートadderは呼び出されることを想定しているため、整合性テストで
// 正しく動くか確認する。
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
テスト用ファイルtests/integration_test.rs:
// 他のコードがするように、テストするクレートを持ってくる
extern crate adder;
#[test]
fn test_add() {
assert_eq!(adder::add(3, 2), 5);
}
テストをcargo testコマンドで実行する
$ cargo test
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Running target/debug/deps/integration_test-bcd60824f5fbfe19
running 1 test
test test_add ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Doc-tests adder
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
testsディレクトリ内の各コードは異なるクレートとしてコンパイルされます。整合性
テスト間でコードを共有したいときはパブリック関数を持つモジュールを定義し、それを
インポートします。
ファイルtests/common.rs:
pub fn setup() {
// 必要なファイルやディレクトリを作る、サーバーを起動する
// などの必要なセットアップを記述する。
}
テストファイルtests/integration_test.rs
// 他のコードがするように、テストするクレートを持ってくる
extern crate adder;
// commonモジュールをインポートする。
mod common;
#[test]
fn test_add() {
// common内のコードを実行する
common::setup();
assert_eq!(adder::add(3, 2), 5);
}
このようなコードのモジュール階層は普通のモジュールの規則と同じであるため、
tests/common/mod.rsのような場所に作ってもOKです。
開発時依存
時々、テストにのみ必要な依存がある場合があります(例えばベンチマーク)
このような依存はCargo.tomlの[dev-dependencies]節に追加してください。
この依存は、このクレートに依存する他のクレートには影響しません。
assert!マクロを拡張するクレートを例に取ってみましょう。
ファイルCargo.toml:
# いつものデータは省略します
[dev-dependencies]
pretty_assertions = "0.4.0"
ファイルsrc/lib.rs:
// クレートをテストのみの使用に制限する
#[cfg(test)]
#[macro_use]
extern crate pretty_assertions;
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
}
こちらも参照
Cargoのドキュメントの「依存を指定する」章
安全でない操作
この節では、まず公式ドキュメントから、「one should try to minimize
the amount of unsafe code in a code base.」(安全でないコードがコードベースに
しめる割合が最小になるよう努力しなければならない。)これを念頭に置いて、始めましょう。
Rustのunsafeアノテーションは、コンパイラによる保護を無効化するのに使います。
unsafeはおもにこれらの4つのことに使われます。
- 生ポインタを参照する
unsafeな関数やメソッドを呼び出す。(FFIから関数を呼び出すときも含みます。 この本の前の章も参照してください。)- 静的可変変数を変更する
- 安全でないトレイトを実装する
生ポインタ
生ポインタ*と参照&Tは似ていますが、参照は有効なデータを参照していることが
ボローチェッカーによっていつも保証されています。生ポインタを参照するにはunsafe
ブロックを使う必要があります。
fn main() { let raw_p: *const u32 = &10; unsafe { assert!(*raw_p == 10); } }
安全でない関数を呼び出す
いくつかの関数はunsafeブロックで定義されていて、これはコンパイラの代わりに
プログラマが正確性を管理しなければいけないことを意味します。一つの例は、最初の
要素のポインタと長さからスライスを作るstd::slice::from_raw_parts関数です。
use std::slice; fn main() { let some_vector = vec![1, 2, 3, 4]; let pointer = some_vector.as_ptr(); let length = some_vector.len(); unsafe { let my_slice: &[u32] = slice::from_raw_parts(pointer, length); assert_eq!(some_vector.as_slice(), my_slice); } }
slice::from_raw_partsは、メモリのポインタが有効で、かつすべて正しい型を持っている
という一種の仮定に基づいています。これはいつも成り立つとは限らず、もし成り立たなかった
ときの動作が未定義で、何が起こるかわかりません。
互換性
Rustは高速に進化するため、できるだけ後方互換性を維持する努力はして いるものの、互換性の問題が起きてしまいます。
Raw識別子
Rustは他のプログラミング言語のように、「キーワード」を持っています。これは言語 レベルで意味がある単語であるため、変数名や、関数名などには使えません。Raw識別子は 本当は許可されていないキーワードを使うのに使われます。主に、これはRustが新しい キーワードを提供したが、ライブラリは古いエディションのRustを使っているため、変数名 や関数名とキーワードがかぶってしまうときに使います・
例えば、クレートfooがRust 2015を使っていて、tryという名前の関数を持っているとします。
Rust 2018では、これはキーワードとして使われているため、 Raw識別子なしではこの関数は
使えません。
extern crate foo;
fn main() {
foo::try();
}
このようなエラーに遭遇するでしょう:
error: expected identifier, found keyword `try`
--> src/main.rs:4:4
|
4 | foo::try();
| ^^^ expected identifier, found keyword
Raw識別子を使うとこのように書けます。
extern crate foo;
fn main() {
foo::r#try();
}
メタデータ
プログラムの書き方とは直接関係はないが、コードを書くに当たって誰にとっても 便利であろうツールやインフラサポートをまとめました。 これらのトピックが含まれます:
- ドキュメンテーション: 組み込みの
rustdocでライブラリドキュメンテーションを書く。 - Playpen: IRust Playpen(Rust Playgroundとしても知られる)とドキュメンテーションの 整合性を取る。
ドキュメンテーション
cargo docでtarget/doc上にドキュメンテーションを作ることができます。
cargo testで(ドキュメンテーションテストを含む)すべてのテストを実行でき、さらに
cargo test --docでドキュメンテーションテストのみ実行できます。
このコマンドはrustdoc(やrustc)と必要に応じて連携をとります。
Docコメント
Docコメントはドキュメントが必要な巨大プロジェクトに有用です。rustdoc
を実行した時、コメントがドキュメントにコンパイルされます。
これは///から始まり、Markdownをサポートしています。
#![crate_name = "doc"]
/// 人類を表す
pub struct Person {
/// ジュリエットがどんなに嫌がろうとも、人は必ず名前を持ちます。
name: String,
}
impl Person {
/// 与えられた名前を持つPersonを返す。
///
/// # 引数
///
/// * `name` - 人の名前を保持する文字列
///
/// # 例
///
/// ```
/// // コメント内のコードは実行できます。
/// // --testを`rustdoc`に渡すと、テストとして機能します!
/// use doc::Person;
/// let person = Person::new("name");
/// ```
pub fn new(name: &str) -> Person {
Person {
name: name.to_string(),
}
}
/// フレンドリーな挨拶!
///
/// `Person`に対して呼び出されたら、"Hello, [name]"と言う。
pub fn hello(& self) {
println!("Hello, {}!", self.name);
}
}
fn main() {
let john = Person::new("John");
john.hello();
}
テストを実行するためには、コードをライブラリとしてビルドし、テストするライブラリ
がどこにあるのかrustdocに教えて、それぞれのテストとリンクできるようにする。
$ rustc doc.rs --crate-type lib
$ rustdoc --test --extern doc="libdoc.rlib" doc.rs
Doc属性
以下は、rustdocで使われる#[doc]属性についての例です。
inline
分けられたページへのリンクの代わりにインラインドキュメント を使う。
#[doc(inline)]
pub use bar::Bar;
/// barドキュメント
mod bar {
/// Barのドキュメント
pub struct Bar;
}
no_inline
別のページからリンクすることを宣言する
// Example from libcore/prelude
#[doc(no_inline)]
pub use crate::mem::drop;
これをドキュメント煮含めないことをrustdocに教える
// Example from the futures-rs library
#[doc(hidden)]
pub use self::async_await::*;
ドキュメンテーション用に、rustdocはコミュニティによって広く使われています。これは標準ライブラリのドキュメント
にも使われています。
こちらも参照:
- The Rust Book: 有用なドキュメンテーションコメントを作る
- The rustdoc Book
- The Reference: Doc comments
- RFC 1574: API Documentation Conventions
- RFC 1946: Relative links to other items from doc comments (intra-rustdoc links)
- Is there any documentation style guide for comments? (reddit)
Playpen
Rust PlaypenはRustコードをWeb上で実験するのに有用です。 このプロジェクトはRust Playgroundとも呼ばれています。
mdbookから使用する
mdbook上では、コード例を実行、編集可能にすることができます。
fn main() { println!("Hello World!"); }
これによって、読者はコードの動作を確認し、また変更して詳しく知ることができます。そのためにはeditableをコンマ区切りで
コードブロックのはじめに追加してください。
```rust,editable
//...ここにコードを書く
```
さらに、ignoreでmdbookでビルド、テストをスキップすることができます。
```rust,editable,ignore
//...ここにコードを書く
```
docsで使う
公式Rustドキュメントに「Run」というボタンがついているのに気づいた人もいるかもしれません。
これはサンプルコードをRust Playgroundで新しいタブに開きます。この機能は#[doc]属性のhtml_playground_urlで
使うことができます。