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ライブラリによって提供されるいくつかのカスタム型について学びます。 -
標準ライブラリその他 - ファイル処理やスレッドのための、さらに多くのカスタム型。
-
テスト - Rust におけるさまざまなテスト。
-
Unsafe 操作 - unsafe 操作のブロックに入る方法について学びます。
-
互換性 - 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 Comments):
///- 後続の項目のドキュメントを生成します//!- それを囲む項目のドキュメントを生成します(通常、ファイルまたはモジュールの先頭で使用されます)
fn main() { // 行コメントは 2 つのスラッシュで始まります。 // スラッシュ以降のすべてはコンパイラに無視されます。 // 例: この行は実行されません // println!("Hello, world!"); // 上のスラッシュを削除して、コードをもう一度実行してみてください。 /* ブロックコメントは、コードを一時的に無効にするのに便利です。 また、ネストすることもできます: /* このように */ これにより 大きなセクションをすばやくコメントアウトしやすくなります。 */ /* * 注: 左側のアスタリスク列は単なるスタイルです - * 言語では必須ではありません。 */ // ブロックコメントを使用すると、スラッシュを 1 つ追加 // または削除するだけでコードのオン/オフを簡単に切り替えられます: /* <- 下のブロック全体をコメント解除するには、ここに '/' を追加します println!("Now"); println!("everything"); println!("executes!"); // 内部の行コメントは影響を受けません // */ // ブロックコメントは式の中でも使用できます: let x = 5 + /* 90 + */ 5; println!("Is `x` 10 or 100? x = {}", x); }
関連項目:
書式付き出力
出力は、std::fmt で定義されている一連の macros によって 処理されます。その一部は次のとおりです:
format!: 書式付きテキストをStringに書き込むprint!:format!と同じですが、テキストはコンソール (io::stdout) に出力されます。println!:print!と同じですが、改行が追加されます。eprint!:print!と同じですが、テキストは標準エラー (io::stderr) に出力されます。eprintln!:eprint!と同じですが、改行が追加されます。
これらはすべて同じ方法でテキストを解析します。さらに、Rust は書式指定の 正しさをコンパイル時にチェックします。
fn main() { // 一般に、`{}` は任意の引数で自動的に置き換えられます。 // これらは文字列化されます。 println!("{} days", 31); // 位置引数を使用できます。`{}` の内側に整数を指定すると、 // どの追加引数に置き換えられるかが決まります。引数は // フォーマット文字列の直後の 0 から始まります。 println!("{0}, this is {1}. {1}, this is {0}", "Alice", "Bob"); // 名前付き引数も使用できます。 println!("{subject} {verb} {object}", object="the lazy dog", subject="the quick brown fox", verb="jumps over"); // `:` の後にフォーマット文字を指定することで、別のフォーマットを // 呼び出せます。 println!("Base 10: {}", 69420); // 69420 println!("Base 2 (binary): {:b}", 69420); // 10000111100101100 println!("Base 8 (octal): {:o}", 69420); // 207454 println!("Base 16 (hexadecimal): {:x}", 69420); // 10f2c // 指定した幅でテキストを右寄せできます。これは // " 1" を出力します。(合計幅 5 のうち、4 つの空白と "1" です。) println!("{number:>5}", number=1); // 数値に余分なゼロを埋められます。 println!("{number:0>5}", number=1); // 00001 // また、記号を反転することで左寄せできます。これは "10000" を出力します。 println!("{number:0<5}", number=1); // 10000 // フォーマット指定子で名前付き引数を使用するには、`$` を付加します。 println!("{number:0>width$}", number=1, width=5); // Rust は、正しい数の引数が使われているかどうかさえチェックします。 println!("My name is {0}, {1} {0}", "Bond"); // FIXME ^ 不足している引数を追加してください: "James" // fmt::Display を実装している型のみが `{}` でフォーマットできます。ユーザー定義の // 型はデフォルトでは fmt::Display を実装していません。 #[allow(dead_code)] // 未使用の項目に警告する `dead_code` を無効化 struct Structure(i32); // これは `Structure` が fmt::Display を実装していないため // コンパイルされません。 // println!("This struct `{}` won't print...", Structure(3)); // TODO ^ この行のコメントを外してみてください // Rust 1.58 以降では、周囲の変数から引数を直接キャプチャできます。 // 上記と同様に、これは " 1"、つまり 4 つの空白と "1" を // 出力します。 let number: f64 = 1.0; let width: usize = 5; println!("{number:>width$}"); }
std::fmt には、テキストの表示を制御する多くの traits が 含まれています。重要なものを 2 つ、その基本形を以下に示します:
fmt::Debug:{:?}マーカーを使用します。デバッグ目的でテキストをフォーマットします。fmt::Display:{}マーカーを使用します。テキストをより洗練された、ユーザーに わかりやすい形式でフォーマットします。
ここでは、標準ライブラリがこれらの型に対する実装を提供しているため、 fmt::Display を使用しました。カスタム型のテキストを出力するには、さらに手順が必要です。
fmt::Display トレイトを実装すると、型を String に変換できる ToString トレイトも自動的に実装されます。
43 行目 では、#[allow(dead_code)] はその後の項目にのみ適用される属性です。
演習
- 上記のコードの問題(FIXME を参照)を修正し、エラーなしで 実行されるようにしてください。
Structure構造体をフォーマットしようとしている行のコメントを外してみてください (TODO を参照)- 表示される小数点以下の桁数を制御して、
Pi is roughly 3.142を出力するprintln!マクロ呼び出しを追加してください。この演習では、pi の推定値としてlet pi = 3.141592を使用してください。(ヒント: 表示する小数点以下の桁数を設定するには、std::fmtドキュメントを確認する必要があるかもしれません)
関連項目:
std::fmt, macros, struct, traits、および dead_code
デバッグ
std::fmt のフォーマット用 traits を使いたいすべての型は、出力可能にするための実装を必要とします。自動実装は、std ライブラリ内の型などに対してのみ提供されます。それ以外の型はすべて、何らかの方法で手動実装されていなければなりません。
fmt::Debug trait は、これを非常に簡単にします。すべての 型は fmt::Debug の実装を derive(自動生成)できます。これは、手動で実装しなければならない fmt::Display には当てはまりません。
#![allow(unused)] fn main() { // この構造体は `fmt::Display` でも `fmt::Debug` でも // 出力できません。 struct UnPrintable(i32); // `derive` 属性は、この `struct` を `fmt::Debug` で出力可能にするために // 必要な実装を自動的に作成します。 #[derive(Debug)] struct DebugPrintable(i32); }
すべての std ライブラリの型も、{:?} で自動的に出力可能です。
// `Structure` に対して `fmt::Debug` の実装を導出します。`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 を手動で実装できます。
関連項目:
attributes、derive、std::fmt、 および struct
Display
fmt::Debug の見た目はコンパクトでもクリーンでもないことが多いため、 出力の見た目をカスタマイズすると有利な場合がよくあります。これは、 {} 出力マーカーを使う fmt::Display を手動で実装することで行います。 実装は次のようになります。
#![allow(unused)] fn main() { // `fmt` モジュールを利用できるように、(`use` で)インポートする。 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> に単一のスタイルを実装した場合、 それはどのようなスタイルであるべきでしょうか?次の 2 つのどちらかでしょうか?
Vec<path>:/:/etc:/home/username:/bin(:で分割)Vec<number>:1,2,3(,で分割)
いいえ。なぜなら、すべての型にとって理想的なスタイルは存在せず、 std ライブラリはそれを決めつけることをしないからです。 fmt::Display は Vec<T> やその他の汎用コンテナには実装されていません。 そのため、これらの汎用的なケースでは fmt::Debug を使う必要があります。
ただし、これは問題ではありません。なぜなら、ジェネリックではない 新しい コンテナ 型であれば、fmt::Display を実装できるからです。
use std::fmt; // `fmt` をインポートする // 2 つの数値を保持する構造体。結果を `Display` と対比できるように、 // `Debug` を derive する。 #[derive(Debug)] struct MinMax(i64, i64); // `MinMax` に対して `Display` を実装する。 impl fmt::Display for MinMax { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // 各位置データ点を参照するために `self.number` を使う。 write!(f, "({}, {})", self.0, self.1) } } // 比較のため、フィールドに名前を付けられる構造体を定義する。 #[derive(Debug)] struct Point2D { x: f64, y: f64, } // 同様に、`Point2D` に対して `Display` を実装する。 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` が実装されている必要があり、 // `Point2D` には実装されていないためである。 // println!("What does Point2D look like in binary: {:b}?", point); }
つまり、fmt::Display は実装されていますが fmt::Binary は実装されていないため、 使用できません。std::fmt にはこのような traits が数多くあり、 それぞれ独自の実装を必要とします。これについては std::fmt でさらに詳しく説明されています。
演習
上の例の出力を確認したら、Point2D 構造体を参考にして、 例に Complex 構造体を追加してください。同じ方法で出力したとき、 出力は次のようになるはずです。
Display: 3.3 +7.2i
Debug: Complex { real: 3.3, imag: 7.2 }
Display: 4.7 -2.3i
Debug: Complex { real: 4.7, imag: -2.3 }
ボーナス: +/- 記号の後にスペースを追加してください。
行き詰まった場合のヒント:
関連項目:
derive、std::fmt、macros、struct、 trait、および use
テストケース: List
要素をそれぞれ順番に処理しなければならない構造体に対して fmt::Display を実装するのは厄介です。問題は、それぞれの write! が fmt::Result を生成することです。これを適切に処理するには、_すべて_の結果を扱う必要があります。Rust はまさにこの目的のために ? 演算子を提供しています。
write! に対して ? を使うと、次のようになります。
// `write!` を試して、エラーになるか確認する。エラーになった場合は、
// そのエラーを返す。それ以外の場合は続行する。
write!(f, "{}", value)?;
? が使えるので、Vec に対して fmt::Display を実装するのは簡単です。
use std::fmt; // `fmt` モジュールをインポートする。 // `Vec` を含む `List` という名前の構造体を定義する。 struct List(Vec<i32>); impl fmt::Display for List { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // `List` 構造体に格納されている Vec<i32> への参照を作成する。 let vec = &self.0; write!(f, "[")?; // 反復のインデックスを `index` として列挙しながら、 // `vec` 内の `v` を反復処理する。 for (index, v) in vec.iter().enumerate() { // 最初の要素を除くすべての要素に、カンマを追加する。 // エラー時に return するために ? 演算子を使う。 if index != 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]
関連項目:
for、ref、Result、struct、 ?、および vec!
フォーマット
フォーマットは_フォーマット文字列_によって指定されることを見てきました。
format!("{}", foo)->"3735928559"format!("0x{:X}", foo)->"0xDEADBEEF"format!("0o{:o}", foo)->"0o33653337357"
同じ変数(foo)でも、使用する_引数型_によって異なる形式でフォーマットできます: X vs o vs 未指定。
このフォーマット機能はトレイトによって実装されており、各引数型に対して1つのトレイトがあります。最も一般的なフォーマット用トレイトは 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!` に似ていますが、フォーマット済みの文字列を // バッファー(最初の引数)に書き込みます。 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 }, ] { 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 }, ] { // 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つ示します。
- 各色を複数回列挙する必要があるかもしれません。
:0>2を使うと、幅 2 になるようにゼロでパディングできます。 16進数には:02Xを使えます。
ボーナス:
- 事前に型キャストを試してみたい場合、RGB色空間で色を計算するための式は
RGB = (R * 65_536) + (G * 256) + Bです。ここでR is RED, G is GREEN, and B is BLUEです。 符号なし8ビット整数(u8)は 255 までの数値しか保持できません。u8をu32にキャストするには、variable_name as u32と書けます。
関連項目:
プリミティブ
Rust はさまざまな primitives へのアクセスを提供します。例を以下に示します。
スカラー型
- 符号付き整数:
i8、i16、i32、i64、i128、およびisize(ポインターサイズ) - 符号なし整数:
u8、u16、u32、u64、u128、およびusize(ポインター サイズ) - 浮動小数点数:
f32、f64 charは'a'、'α'、'∞'のような Unicode スカラー値(各 4 バイト)boolはtrueまたは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; /* 複合型 - 配列とタプル */ // 配列のシグネチャは、型 T と長さから [T; length] として構成されます。 let my_array: [i32; 5] = [1, 2, 3, 4, 5]; // タプルは異なる型の値のコレクションであり、 // 丸括弧 () を使用して構築されます。 let my_tuple = (5u32, 1u8, true, -5.04f32); }
関連項目:
std ライブラリ、mut、inference、および shadowing
リテラルと演算子
整数 1、浮動小数点数 1.2、文字 'a'、文字列 "abc"、真偽値 true およびユニット型 () は、リテラルを使って表現できます。
整数は、代わりに 16 進数、8 進数、2 進数表記で表現することもでき、 それぞれ次のプレフィックスを使用します: 0x、0o、0b。
数値リテラルには、可読性を高めるためにアンダースコアを挿入できます。たとえば、 1_000 は 1000 と同じで、0.000_001 は 0.000001 と同じです。
Rust は科学的な E記法 もサポートしています。たとえば、1e6、7.6e-4 です。 関連付けられる型は f64 です。
使用するリテラルの型をコンパイラに伝える必要があります。ここでは、 リテラルが符号なし 32 ビット整数であることを示すために u32 サフィックスを使用し、 符号付き 32 ビット整数であることを示すために i32 サフィックスを使用します。
利用可能な演算子とその優先順位は、Rust におけるものと同様に、 他の C に似た言語 と似ています。
fn main() { // 整数の加算 println!("1 + 2 = {}", 1u32 + 2); // 整数の減算 println!("1 - 2 = {}", 1i32 - 2); // TODO ^ 型が重要である理由を確認するために、`1i32` を `1u32` に変更してみてください // 科学的記数法 println!("1e4 is {}, -2.5e-3 is {}", 1e4, -2.5e-3); // 短絡評価を行うブール論理 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); }
タプル
タプルは、異なる型の値のコレクションです。タプルは括弧 () を使って構築され、各タプル自体は型シグネチャ (T1, T2, ...) を持つ値です。ここで、T1、T2 はそのメンバーの型です。タプルは任意の数の値を保持できるため、関数は複数の値を返すためにタプルを使用できます。
// タプルは関数の引数および戻り値として使用できます。 fn reverse(pair: (i32, bool)) -> (bool, i32) { // `let` はタプルのメンバーを変数に束縛するために使用できます。 let (int_param, bool_param) = pair; (bool_param, int_param) } // 以下の構造体は演習用です。 #[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); // しかし、長いタプル(12個を超える要素)は出力できません。 //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)); // 1要素のタプルを作成するには、括弧で囲まれたリテラルと区別するために // コンマが必要です。 println!("One element tuple: {:?}", (5u32,)); 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); }
演習
-
まとめ: 上記の例の
Matrix構造体にfmt::Displayトレイトを追加し、デバッグ形式{:?}での出力から表示形式{}での出力に切り替えると、次の出力が表示されるようにしてください。( 1.1 1.2 ) ( 2.1 2.2 )表示形式での出力の例を参照するとよいでしょう。
-
reverse関数をテンプレートとして使用し、行列を引数として受け取り、2つの要素を入れ替えた行列を返す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; length] の一部です。
スライスは配列に似ていますが、その長さはコンパイル時にはわかりません。 代わりに、スライスは 2 ワードのオブジェクトです。最初のワードはデータへのポインター、 2 番目のワードはスライスの長さです。ワードサイズは usize と同じで、 プロセッサーアーキテクチャによって決まります。たとえば、x86-64 では 64 ビットです。スライスは 配列の一部を借用するために使用でき、型シグネチャは &[T] です。
use std::mem; // この関数はスライスを借用します。 fn analyze_slice(slice: &[i32]) { println!("スライスの最初の要素: {}", slice[0]); println!("スライスには {} 個の要素があります", slice.len()); } fn main() { // 固定長配列(型シグネチャは冗長です)。 let xs: [i32; 5] = [1, 2, 3, 4, 5]; // すべての要素を同じ値で初期化できます。 let ys: [i32; 500] = [0; 500]; // インデックスは 0 から始まります。 println!("配列の最初の要素: {}", xs[0]); println!("配列の 2 番目の要素: {}", xs[1]); // `len` は配列内の要素数を返します。 println!("配列内の要素数: {}", xs.len()); // 配列はスタックに割り当てられます。 println!("配列は {} バイトを占有します", mem::size_of_val(&xs)); // 配列はスライスとして自動的に借用できます。 println!("配列全体をスライスとして借用します。"); analyze_slice(&xs); // スライスは配列の一部を指すことができます。 // 形式は [starting_index..ending_index] です。 // `starting_index` はスライス内の最初の位置です。 // `ending_index` はスライス内の最後の位置より 1 つ大きい値です。 println!("配列の一部をスライスとして借用します。"); analyze_slice(&ys[1 .. 4]); // 空のスライス `&[]` の例: let empty_array: [u32; 0] = []; assert_eq!(&empty_array, &[]); assert_eq!(&empty_array, &[][..]); // 同じですが、より冗長です // 配列は `.get` を使って安全にアクセスできます。これは // `Option` を返します。以下に示すようにマッチさせることも、 // プログラムをそのまま続行させる代わりに、適切なメッセージ付きで // 終了させたい場合は `.expect()` とともに使用することもできます。 for i in 0..xs.len() + 1 { // おっと、1 要素分行き過ぎです! match xs.get(i) { Some(xval) => println!("{}: {}", i, xval), None => println!("ゆっくり進んでください!{} は遠すぎます!", i), } } // 定数値で配列を範囲外インデックス指定すると、コンパイル時エラーが発生します。 //println!("{}", xs[5]); // スライスを範囲外インデックス指定すると、実行時エラーが発生します。 //println!("{}", xs[..][5]); }
カスタム型
Rust のカスタムデータ型は、主に次の 2 つのキーワードによって定義されます。
struct: 構造体を定義するenum: 列挙型を定義する
定数は const と static キーワードによって作成することもできます。
構造体
struct キーワードを使って作成できる構造体("structs")には、次の 3 種類があります。
- タプル構造体。基本的には名前付きタプルです。
- 従来の C の構造体
- ユニット構造体。フィールドを持たず、ジェネリクスで有用です。
// 未使用コードの警告を非表示にする属性。 #![allow(dead_code)] #[derive(Debug)] struct Person { name: String, age: u8, } // ユニット構造体 struct Unit; // タプル構造体 struct Pair(i32, f32); // 2 つのフィールドを持つ構造体 struct Point { x: f32, y: f32, } // 構造体は別の構造体のフィールドとして再利用できる struct Rectangle { // 長方形は、空間内で左上と右下の角がどこにあるかによって // 指定できる。 top_left: Point, bottom_right: Point, } fn main() { // フィールド初期化省略記法で構造体を作成する let name = String::from("Peter"); let age = 27; let peter = Person { name, age }; // デバッグ構造体を出力する println!("{:?}", peter); // `Point` をインスタンス化する let point: Point = Point { x: 5.2, y: 0.4 }; let another_point: Point = Point { x: 10.3, y: 0.2 }; // point のフィールドにアクセスする println!("point coordinates: ({}, {})", point.x, point.y); // 構造体更新構文を使って、もう一方のフィールドを利用しながら // 新しい点を作成する let bottom_right = Point { x: 10.3, ..another_point }; // そのフィールドを `another_point` から使ったため、`bottom_right.y` は // `another_point.y` と同じになる println!("second point: ({}, {})", bottom_right.x, bottom_right.y); // `let` 束縛を使って point を分配束縛する let Point { x: left_edge, y: top_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); }
アクティビティ
Rectangleの面積を計算する関数rect_areaを追加してください(ネストした分配束縛を使ってみてください)。Pointとf32を引数に取り、その点を左上の角とし、幅と高さが そのf32に対応するRectangleを返す関数squareを追加してください。
関連項目
attributes、raw identifiers、および destructuring
列挙型
enumキーワードを使うと、いくつかの異なるバリアントのいずれかになり得る型を作成できます。structとして有効なバリアントは、すべてenumでも有効です。
// Webイベントを分類するための`enum`を作成します。名前と型情報の // 両方が一緒にバリアントを指定していることに注目してください: // `PageLoad != PageUnload`かつ`KeyPress(char) != Paste(String)`です。 // それぞれが異なり、独立しています。 enum WebEvent { // `enum`バリアントは`unit-like`でも、 PageLoad, PageUnload, // タプル構造体のようなものでも、 KeyPress(char), Paste(String), // あるいはC風構造体でもかまいません。 Click { x: i64, y: i64 }, } // `WebEvent`列挙型を引数として受け取り、 // 何も返さない関数。 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 VeryVerboseEnumOfThingsToDoWithNumbers { Add, Subtract, } // 型エイリアスを作成します type Operations = VeryVerboseEnumOfThingsToDoWithNumbers; fn main() { // 長くて不便な名前ではなく、そのエイリアスを介して各バリアントを // 参照できます。 let x = Operations::Add; }
これを最もよく見かける場所は、Selfエイリアスを使用するimplブロック内です。
enum VeryVerboseEnumOfThingsToDoWithNumbers { Add, Subtract, } impl VeryVerboseEnumOfThingsToDoWithNumbers { fn run(&self, x: i32, y: i32) -> i32 { match self { Self::Add => x + y, Self::Subtract => x - y, } } }
列挙型と型エイリアスについてさらに詳しく知るには、この機能がRustで安定化されたときの 安定化レポートを読むことができます。
関連項目:
match、fn、String、および「型エイリアス列挙型バリアント」RFC
use
use 宣言を使うと、名前にアクセスするためにモジュールパス全体を入力しなくて済みます:
// 未使用コードに関する警告を非表示にする属性。 #![allow(dead_code)] enum Stage { Beginner, Advanced, } enum Role { Student, Teacher, } fn main() { // 各名前を明示的に `use` して、手動でスコープ指定しなくても // 利用できるようにする。 use Stage::{Beginner, Advanced}; // `Role` 内の各名前を自動的に `use` する。 use Role::*; // `Stage::Beginner` と同等。 let stage = Beginner; // `Role::Student` と同等。 let role = Student; match stage { // 上記の明示的な `use` により、スコープ指定がないことに注目。 Beginner => println!("Beginners are starting their learning journey!"), Advanced => println!("Advanced learners are mastering their subjects..."), } match role { // ここでもスコープ指定がないことに注目。 Student => println!("Students are acquiring knowledge!"), Teacher => println!("Teachers are spreading knowledge!"), } }
関連項目:
C 風
enum は C 風の列挙型としても使用できます。
// 未使用コードの警告を隠す属性。 #![allow(dead_code)] // 暗黙的な判別子を持つ enum(0 から始まる) enum Number { Zero, One, Two, } // 明示的な判別子を持つ enum enum Color { Red = 0xff0000, Green = 0x00ff00, Blue = 0x0000ff, } fn main() { // `enum` は整数にキャストできます。 println!("zero is {}", Number::Zero as i32); println!("one is {}", Number::One as i32); println!("roses are #{:06x}", Color::Red as u32); println!("violets are #{:06x}", Color::Blue as u32); }
関連項目:
テストケース: 連結リスト
連結リストを実装する一般的な方法の 1 つは、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)) } // リストの長さを返す fn len(&self) -> u32 { // このメソッドの振る舞いは `self` のバリアントに依存するため、 // `self` をマッチする必要がある // `self` の型は `&List` で、`*self` の型は `List`。参照 `&T` に対するマッチよりも // 具象型 `T` に対するマッチが推奨される // Rust 2018 以降では、ここで self を使用し、下でも tail(ref なし)を使用できる。 // rust は & と ref tail を推論する。 // https://doc.rust-lang.org/edition-guide/rust-2018/ownership-and-lifetimes/default-match-bindings.html を参照 match *self { // `self` は借用されているため、tail の所有権を取得できない。 // 代わりに tail への参照を取得する // また、これは非末尾再帰呼び出しであり、長いリストではスタックオーバーフローを引き起こす可能性がある。 Cons(_, ref tail) => 1 + tail.len(), // 基底ケース: 空のリストの長さは 0 Nil => 0 } } // リストの表現を(ヒープに割り当てられた)文字列として返す 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!("連結リストの長さ: {}", list.len()); println!("{}", list.stringify()); }
関連項目:
定数
Rust には、グローバルを含む任意のスコープで宣言できる 2 種類の定数があります。どちらも明示的な型注釈が必要です。
const: 変更できない値(一般的なケース)。static:'staticライフタイムを持つ、可変である可能性のある変数。 static ライフタイムは推論されるため、指定する必要はありません。 可変 static 変数にアクセスまたは変更することはunsafeです。
// グローバルは他のすべてのスコープの外側で宣言されます。 static LANGUAGE: &str = "Rust"; const THRESHOLD: i32 = 10; fn is_big(n: i32) -> bool { // 何らかの関数で定数にアクセスする n > THRESHOLD } fn main() { let n = 16; // メインスレッドで定数にアクセスする println!("This is {}", LANGUAGE); println!("The threshold is {}", THRESHOLD); println!("{} is {}", n, if is_big(n) { "big" } else { "small" }); // エラー!`const` は変更できません。 THRESHOLD = 5; // FIXME ^ この行をコメントアウトしてください }
関連項目:
The const/static RFC, 'static ライフタイム
変数束縛
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; }
コンパイラは、可変性エラーに関する詳細な診断を出力します。
スコープとシャドーイング
変数束縛にはスコープがあり、_ブロック_内で生存するよう制約されます。ブロックとは、中括弧 {} で囲まれた文の集まりです。
fn main() { // この束縛は main 関数内で生存する let long_lived_binding = 1; // これはブロックであり、main 関数よりも小さいスコープを持つ { // この束縛はこのブロック内にのみ存在する let short_lived_binding = 2; println!("inner short: {}", short_lived_binding); } // ブロックの終わり // エラー!`short_lived_binding` はこのスコープには存在しない println!("outer short: {}", short_lived_binding); // FIXME ^ この行をコメントアウトする println!("outer long: {}", long_lived_binding); }
また、変数のシャドーイングも許可されています。
fn main() { let shadowed_binding = 1; { println!("before being shadowed: {}", shadowed_binding); // この束縛は外側の束縛を*シャドーイング*する let shadowed_binding = "abc"; println!("shadowed in inner block: {}", shadowed_binding); } println!("outside inner block: {}", shadowed_binding); // この束縛は前の束縛を*シャドーイング*する let shadowed_binding = 2; println!("shadowed in outer block: {}", shadowed_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; // エラー!変換ルールには制限がある。 // 浮動小数点数は char に直接変換できない。 let character = decimal as char; // FIXME ^ この行をコメントアウトする println!("Casting: {} -> {} -> {}", decimal, integer, character); // 任意の値を符号なし型 T にキャストするとき、 // 上記のように #![allow(overflowing_literals)] lint が指定されている場合に限り、 // 値が新しい型に収まるまで T::MAX + 1 が加算または減算される。 // そうでない場合はコンパイラエラーになる。 // 1000 はすでに u16 に収まる println!("1000 as a u16 is: {}", 1000 as u16); // 1000 - 256 - 256 - 256 = 232 // 内部的には、最下位ビット(LSB)側の最初の 8 ビットが保持され、 // 最上位ビット(MSB)側に向かう残りのビットは切り捨てられる。 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); // 境界ケースでは、8 ビットの 2 の補数表現における値 128 は -128 になる println!(" 128 as a i8 is : {}", 128 as i8); // 上の例を繰り返す // 1000 as u8 -> 232 println!("1000 as a u8 is : {}", 1000 as u8); // そして、8 ビットの 2 の補数表現における 232 の値は -24 になる println!(" 232 as a i8 is : {}", 232 as i8); // Rust 1.45 以降、浮動小数点数から整数へキャストするとき、 // `as` キーワードは *飽和キャスト* を行う。 // 浮動小数点値が上限を超えるか下限未満の場合、 // 返される値は超えた境界値と等しくなる。 // 300.0 as u8 は 255 println!(" 300.0 as u8 is : {}", 300.0_f32 as u8); // -100.0 as u8 は 0 println!("-100.0 as u8 is : {}", -100.0_f32 as u8); // nan as u8 は 0 println!(" nan as u8 is : {}", f32::NAN as u8); // この動作にはわずかな実行時コストがかかるが、 // unsafe なメソッドを使えば回避できる。ただし、結果がオーバーフローして // **不健全な値** を返す可能性がある。これらのメソッドは慎重に使うこと: unsafe { // 300.0 as u8 は 44 println!(" 300.0 as u8 is : {}", 300.0_f32.to_int_unchecked::<u8>()); // -100.0 as u8 は 156 println!("-100.0 as u8 is : {}", (-100.0_f32).to_int_unchecked::<u8>()); // nan as u8 は 0 println!(" nan as u8 is : {}", f32::NAN.to_int_unchecked::<u8>()); } }
リテラル
数値リテラルには、型をサフィックスとして追加することで型注釈を付けることができます。たとえば、 リテラル 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`、`Inch`、`U64` は `u64` の新しい名前です。 type NanoSecond = u64; type Inch = u64; type U64 = u64; fn main() { // `NanoSecond` = `Inch` = `U64` = `u64`。 let nanoseconds: NanoSecond = 5 as u64; let inches: Inch = 2 as U64; // 型エイリアスは追加の型安全性を提供しないことに注意してください。なぜなら、 // エイリアスは新しい型では*ない*からです println!("{} nanoseconds + {} inches = {} unit?", nanoseconds, inches, nanoseconds + inches); }
エイリアスの主な用途はボイラープレートを減らすことです。たとえば、io::Result<T> 型は Result<T, io::Error> 型のエイリアスです。
関連項目:
変換
プリミティブ型は、キャストによって相互に変換できます。
Rust は、トレイトを使用してカスタム型(すなわち、struct と enum)間の変換に 対応します。汎用的な 変換では、From と Into トレイトを使用します。ただし、より一般的なケース、特に String への変換および String からの変換には、より具体的なものがあります。
From と Into
From トレイトと Into トレイトは本質的に結び付いており、これは実際に その実装の一部です。型 B から型 A に変換できるなら、型 B を型 A に 変換できるはずだと考えるのは自然でしょう。
From
From トレイトを使うと、ある型が別の型から自身を作成する方法を定義できます。 したがって、複数の型の間で変換するための非常に単純な仕組みを提供します。 標準ライブラリには、プリミティブ型や一般的な型の変換のために、このトレイトの実装が 数多く用意されています。
たとえば、str を String に簡単に変換できます。
#![allow(unused)] 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 トレイトの逆です。 ある型を別の型へ変換する方法を定義します。
into() を呼び出す際には、多くの場合コンパイラが結果の型を判定できないため、 通常は結果の型を指定する必要があります。
use std::convert::Into; #[derive(Debug)] struct Number { value: i32, } impl Into<Number> for i32 { fn into(self) -> Number { Number { value: self } } } fn main() { let int = 5; // 型注釈を削除してみてください let num: Number = int.into(); println!("My number is {:?}", num); }
From と Into は相互に置き換え可能
From と Into は補完し合うように設計されています。 両方のトレイトに対して実装を提供する必要はありません。 自分の型に対して From トレイトを実装していれば、必要なときに Into が それを呼び出します。ただし、その逆は成り立たないことに注意してください。自分の型に対して Into を実装しても、From の実装が自動的に提供されるわけではありません。
use std::convert::From; #[derive(Debug)] struct Number { value: i32, } // `From` を定義 impl From<i32> for Number { fn from(item: i32) -> Self { Number { value: item } } } fn main() { let int = 5; // `Into` を使用 let num: Number = int.into(); println!("My number is {:?}", num); }
TryFrom と TryInto
From と 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(())); }
文字列への変換と文字列からの変換
String への変換
任意の型を String に変換するには、その型に ToString トレイトを実装するだけで済みます。ただし、直接そうするのではなく、 fmt::Display トレイトを実装すべきです。これにより ToString が自動的に提供され、 print! のセクションで説明したように、その型を出力できるようにもなります。
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()); }
文字列のパース
文字列を多くの型に変換できると便利ですが、より一般的な文字列操作の 1 つは、 文字列から数値に変換することです。これに対する慣用的なアプローチは、 parse 関数を使用し、型推論が働くようにするか、'turbofish' 構文を使って パースする型を指定することです。次の例では、両方の方法を示しています。
これは、その型に FromStr トレイトが実装されている限り、文字列を指定された型に変換します。 これは標準ライブラリ内の多数の型に対して実装されています。
fn main() { let parsed: i32 = "5".parse().unwrap(); let turbo_parsed = "10".parse::<i32>().unwrap(); let sum = parsed + turbo_parsed; println!("Sum: {:?}", sum); }
ユーザー定義型でこの機能を利用するには、その型に FromStr トレイトを実装するだけです。
use std::num::ParseIntError; use std::str::FromStr; #[derive(Debug)] struct Circle { radius: i32, } impl FromStr for Circle { type Err = ParseIntError; fn from_str(s: &str) -> Result<Self, Self::Err> { match s.trim().parse() { Ok(num) => Ok(Circle{ radius: num }), Err(e) => Err(e), } } } fn main() { let radius = " 3 "; let circle: Circle = radius.parse().unwrap(); println!("{:?}", circle); }
式
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_cubed = x_squared * x; // この式が `y` に代入されます x_cubed + 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"); // この式は `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!("無限まで数えましょう!"); // 無限ループ loop { count += 1; if count == 3 { println!("3"); // このイテレーションの残りをスキップ continue; } println!("{}", count); if count == 5 { println!("OK、もう十分です"); // このループを抜ける break; } } }
ネストとラベル
ネストされたループを扱うときに、外側のループに対して break や continue を実行できます。このような場合、ループには何らかの 'label を付けて注釈し、そのラベルを break/continue 文に渡す必要があります。
#![allow(unreachable_code, unused_labels)] 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 の用途の 1 つは、操作が成功するまで再試行することです。ただし、その操作が値を返す場合は、その値を残りのコードに渡す必要があるかもしれません。その値を 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 構文は、Iterator を反復処理するために使用できます。 イテレータを作成する最も簡単な方法の 1 つは、範囲記法 a..b を使用することです。これは a(含む)から b (含まない)までの値を 1 ずつ生成します。
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); } } }
a>b の場合でもコードはコンパイルできますが、ループは 決して実行されないことに注意してください。
for i in 10..1{ println!("fizzbuzz"); }
カウントダウンしたい場合は、代わりに .rev() を使用する必要があります
for i in (1..10).rev(){ println!("fizzbuzz"); }
for とイテレータ
for in 構文は、いくつかの方法で Iterator と相互作用できます。 Iterator トレイトのセクションで説明したように、デフォルトでは 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!"), // TODO ^ & を削除して、単に "Ferris" とマッチさせてみてください _ => println!("Hello {}", name), } } println!("names: {:?}", names); }
into_iter- これはコレクションを消費するため、各反復で実際の データが提供されます。コレクションが消費されると、ループ内で「ムーブ」 されたため、再利用できなくなります。
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), } } // `names` は「ムーブ」されており、もう使用できません。 // コンパイラエラーを確認するには、下の行をアンコメントしてみてください。 // println!("names: {:?}", names); }
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 は match キーワードによってパターンマッチングを提供します。これは C の switch のように使用できます。最初にマッチしたアームが評価され、すべての可能な値が 網羅されている必要があります。
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"), // TODO ^ 素数の値のリストに 13 を追加してみる // 包含範囲にマッチ 13..=19 => println!("A teen"), // 残りのケースを処理 _ => println!("Ain't special"), // TODO ^ このキャッチオールアームをコメントアウトしてみる } let boolean = true; // match も式である let binary = match boolean { // match のアームは、すべての可能な値を網羅していなければならない false => 0, true => 1, // TODO ^ これらのアームのいずれかをコメントアウトしてみる }; println!("{} -> {}", boolean, binary); }
デストラクチャリング
match ブロックでは、さまざまな方法でアイテムをデストラクチャリングできます。
関連項目:
デストラクチャリングに関する The Rust Reference
タプル
タプルは、次のように match で分配束縛できます:
fn main() { let triple = (0, -2, 3); // TODO ^ `triple` に別の値を試してみてください println!("{:?} について教えて", triple); // match はタプルを分配束縛するために使用できます match triple { // 2番目と3番目の要素を分配束縛 (0, y, z) => println!("最初は `0`、`y` は {:?}、そして `z` は {:?}", y, z), (1, ..) => println!("最初は `1` で、残りは重要ではありません"), (.., 2) => println!("最後は `2` で、残りは重要ではありません"), (3, .., 4) => println!("最初は `3`、最後は `4` で、残りは重要ではありません"), // `..` はタプルの残りを無視するために使用できます _ => println!("それらが何であっても重要ではありません"), // `_` は値を変数に束縛しないことを意味します } }
関連項目:
配列/スライス
タプルと同様に、配列とスライスは次のように分配束縛できます。
fn main() { // 配列内の値を変更したり、スライスにしたりしてみてください! let array = [1, -2, 6]; match array { // 2 番目と 3 番目の要素をそれぞれの変数に束縛します [0, second, third] => println!("array[0] = 0, array[1] = {}, array[2] = {}", second, third), // 単一の値は _ で無視できます [1, _, third] => println!( "array[0] = 1, array[2] = {}、array[1] は無視されました", third ), // 一部を束縛し、残りを無視することもできます [-1, second, ..] => println!( "array[0] = -1, array[1] = {}、その他すべては無視されました", second ), // 以下のコードはコンパイルされません // [-1, second] => ... // または、それらを別の配列/スライスに格納できます(型は // マッチ対象の値の型によって決まります) [3, second, tail @ ..] => println!( "array[0] = 3, array[1] = {}、他の要素は {:?} です", second, tail ), // これらのパターンを組み合わせることで、たとえば、最初と最後の値を // 束縛し、残りを単一の配列に格納できます [first, middle @ .., last] => println!( "array[0] = {}, middle = {:?}, array[2] = {}", first, middle, last ), } }
関連項目:
列挙型
enum も同様に分配束縛できます:
// 1つのバリアントしか使われないため、警告を抑制するには // `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!("これは何色ですか?"); // `enum` は `match` を使って分配束縛できます。 match color { Color::Red => println!("色は赤です!"), Color::Blue => println!("色は青です!"), Color::Green => println!("色は緑です!"), Color::RGB(r, g, b) => println!("赤: {}、緑: {}、青: {}!", r, g, b), Color::HSV(h, s, v) => println!("色相: {}、彩度: {}、明度: {}!", h, s, v), Color::HSL(h, s, l) => println!("色相: {}、彩度: {}、輝度: {}!", h, s, l), Color::CMY(c, m, y) => println!("シアン: {}、マゼンタ: {}、イエロー: {}!", c, m, y), Color::CMYK(c, m, y, k) => println!("シアン: {}、マゼンタ: {}、イエロー: {}、キー(黒): {}!", c, m, y, k), // すべてのバリアントが調べられているため、別のアームは不要です } }
関連項目:
#[allow(...)]、カラーモデル、および enum
ポインター/ref
ポインターについては、分配束縛と参照外しは異なる概念であり、C/C++ のような言語とは異なる使われ方をするため、区別する必要があります。
- 参照外しには
*を使用します - 分配束縛には
&、ref、ref mutを使用します
fn main() { // `i32` 型の参照を代入します。`&` は、参照が代入されている // ことを示します。 let reference = &4; match reference { // `reference` が `&val` に対してパターンマッチされると、 // 次のような比較になります: // `&i32` // `&val` // ^ マッチする `&` を取り除くと、`i32` が `val` に // 代入されるべきであることがわかります。 &val => println!("分配束縛によって値を取得しました: {:?}", val), } // `&` を避けるには、マッチする前に参照外しします。 match *reference { val => println!("参照外しによって値を取得しました: {:?}", val), } // 参照から始めない場合はどうなるでしょうか?`reference` が `&` だったのは、 // 右辺がすでに参照だったためです。これは右辺が参照ではないため、 // 参照ではありません。 let _not_a_reference = 3; // Rust はまさにこの目的のために `ref` を提供しています。これは代入を // 変更し、その要素への参照が作成されるようにします。そしてこの // 参照が代入されます。 let ref _is_a_reference = 3; // したがって、参照を持たない 2 つの値を定義することで、 // `ref` と `ref mut` によって参照を取得できます。 let value = 5; let mut mut_value = 6; // `ref` キーワードを使用して参照を作成します。 match value { ref r => println!("値への参照を取得しました: {:?}", r), } // `ref mut` も同様に使用します。 match mut_value { ref mut m => { // 参照を取得しました。何かを加算する前に // 参照外しする必要があります。 *m += 10; println!("10 を加算しました。`mut_value`: {:?}", m); }, } }
関連項目:
構造体
同様に、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!("x の最初の値は 1、b = {}, y = {} ", b, y), // 構造体を分解し、変数名を変更できます。 // 順序は重要ではありません Foo { y: 2, x: i } => println!("y は 2、i = {:?}", i), // また、一部の変数を無視することもできます: Foo { y, .. } => println!("y = {}、x は気にしません", y), // これはエラーになります: パターンがフィールド `x` に言及していません //Foo { y } => println!("y = {}", y), } let faa = Foo { x: (1, 2), y: 3 }; // 構造体を分解するために match ブロックは必要ありません: let Foo { x : x0, y: y0 } = faa; println!("外側: x0 = {x0:?}, y0 = {y0}"); // 分解はネストした構造体でも機能します: struct Bar { foo: Foo, } let bar = Bar { foo: faa }; let Bar { foo: Foo { x: nested_x, y: nested_y } } = bar; println!("ネスト: nested_x = {nested_x:?}, nested_y = {nested_y:?}"); }
関連項目:
ガード
match の_ガード_を追加して、アームをフィルターできます。
#[allow(dead_code)] enum Temperature { Celsius(i32), Fahrenheit(i32), } fn main() { let temperature = Temperature::Celsius(35); // ^ TODO `temperature` に別の値を試す match temperature { Temperature::Celsius(t) if t > 30 => println!("{}C is above 30 Celsius", t), // `if condition` の部分 ^ がガード Temperature::Celsius(t) => println!("{}C is equal to or below 30 Celsius", t), Temperature::Fahrenheit(t) if t > 86 => println!("{}F is above 86 Fahrenheit", t), Temperature::Fahrenheit(t) => println!("{}F is equal to or below 86 Fahrenheit", t), } }
コンパイラは、すべてのパターンが match 式で網羅されているかを確認するときに、 ガード条件を考慮しないことに注意してください。
fn main() { let number: u8 = 4; match number { i if i == 0 => println!("Zero"), i if i > 0 => println!("Greater than zero"), // _ => unreachable!("Should never happen."), // TODO ^ コンパイルを修正するにはコメントアウトを解除する } }
関連項目:
バインディング
変数に間接的にアクセスすると、再バインドせずにその変数で分岐して使用することはできません。match は、値を名前にバインドするための @ 記号を提供します。
// `u32` を返す関数 `age`。 fn age() -> u32 { 15 } fn main() { println!("Tell me what type of person you are"); match age() { 0 => println!("I haven't celebrated my first birthday yet"), // 1 ..= 12 に対して直接 `match` することもできるが、その場合 // 子どもの年齢はいくつになるのか? // `match` n として `if` ガードを使うこともできるが、 // 網羅性チェックには寄与しない。 // (ただし、この場合は末尾に「キャッチオール」パターンがあるため // 問題にはならない) // 代わりに、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), // 複数の値をマッチする場合にも、同様のバインディングを行える。 n @ (1 | 7 | 15 | 13) => println!("I'm a teen of age {:?}", n), // 何もバインドしない。結果を返す。 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` バリアントを取得し、その値が `n` にバインドされ、 // 42 と等しい場合にマッチする。 // `Some(42)` を使って `"The Answer: 42!"` を出力することもできるが、 // 後でそれを変更したくなった場合、 // `42` を 2 か所で変更する必要がある。 // `Some(n) if n == 42` を使って `"The Answer: {n}!"` を出力することもできるが、 // 網羅性チェックには寄与しない。 // (ただし、この場合は次のアームが「キャッチオール」パターンであるため // 問題にはならない) Some(n @ 42) => println!("The Answer: {}!", n), // その他の数値にマッチする。 Some(n) => println!("Not interesting... {}", n), // それ以外(`None` バリアント)にマッチする。 _ => (), } }
関連項目:
if let
一部のユースケースでは、enum をマッチする際に match は扱いにくくなります。例:
#![allow(unused)] fn main() { // `Option<i32>` 型の `optional` を作成する let optional = Some(7); match optional { Some(i) => println!("This is a really long string and `{:?}`", i), _ => {}, // ^ `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"); } }
もう 1 つの利点は、if let によってパラメーターを持たない enum バリアントをマッチできることです。これは、enum が PartialEq を実装または derive していない場合でも当てはまります。そのような場合、enum のインスタンス同士を等価比較できないため if Foo::Bar == a はコンパイルに失敗しますが、if let は引き続き機能します。
挑戦してみますか? 次の例を修正して if let を使用してください。
// この enum は意図的に PartialEq を実装も derive もしていません。 // そのため、以下の 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"); } }
関連項目:
let-else
🛈 rust 1.65 以降で安定化
🛈 次のようにコンパイルすることで、特定のエディションを対象にできます
rustc --edition=2021 main.rs
let-else を使うと、反駁可能パターンは通常の let と同様に周囲のスコープで変数をマッチして束縛できます。あるいは、パターンがマッチしない場合に発散できます(例: break、return、panic!)。
use std::str::FromStr; fn get_count_item(s: &str) -> (u64, &str) { let mut it = s.split(' '); let (Some(count_str), Some(item)) = (it.next(), it.next()) else { panic!("Can't segment count item pair: '{s}'"); }; let Ok(count) = u64::from_str(count_str) else { panic!("Can't parse integer: '{count_str}'"); }; (count, item) } fn main() { assert_eq!(get_count_item("3 chairs"), (3, "chairs")); }
名前束縛のスコープこそが、これを match や if let-else 式と異なるものにしている主な点です。以前は、不都合な少しの繰り返しと外側の let によって、これらのパターンを近似できました。
#![allow(unused)] fn main() { use std::str::FromStr; fn get_count_item(s: &str) -> (u64, &str) { let mut it = s.split(' '); let (count_str, item) = match (it.next(), it.next()) { (Some(count_str), Some(item)) => (count_str, item), _ => panic!("Can't segment count item pair: '{s}'"), }; let count = if let Ok(count) = u64::from_str(count_str) { count } else { panic!("Can't parse integer: '{count_str}'"); }; (count, item) } assert_eq!(get_count_item("3 chairs"), (3, "chairs")); }
関連項目:
option、match、if let、および let-else RFC。
while let
if let と同様に、while let はぎこちない match の並びを より扱いやすくできます。i をインクリメントする次の並びを考えてみましょう。
#![allow(unused)] fn main() { // `Option<i32>` 型の `optional` を作成する let mut optional = Some(0); // このテストを繰り返し試行する。 loop { match optional { // `optional` が分解できた場合、ブロックを評価する。 Some(i) => { if i > 9 { println!("Greater than 9, quit!"); optional = None; } else { println!("`i` is `{:?}`. Try again.", i); 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 { fizzbuzz(n); } }
関連関数とメソッド
一部の関数は、特定の型に結び付いています。これには 2 つの形式があります: 関連関数とメソッドです。関連関数は一般的に型に対して定義される関数であり、メソッドは型の特定のインスタンスに対して呼び出される関連関数です。
struct Point { x: f64, y: f64, } // 実装ブロック。すべての `Point` の関連関数とメソッドはここに入ります impl Point { // この関数は特定の型、つまり 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!("Pair({}, {}) を破棄しています", first, second); // `first` と `second` はスコープを抜け、解放されます } } fn main() { let rectangle = Rectangle { // 関連関数はダブルコロンを使って呼び出されます p1: Point::origin(), p2: Point::new(3.0, 4.0), }; // メソッドはドット演算子を使って呼び出されます // 最初の引数 `&self` は暗黙的に渡されることに注意してください。すなわち、 // `rectangle.perimeter()` === `Rectangle::perimeter(&rectangle)` です println!("Rectangle の周囲の長さ: {}", rectangle.perimeter()); println!("Rectangle の面積: {}", 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 ^ この行のコメントを解除してみてください }
クロージャ
クロージャは、周囲の環境をキャプチャできる関数です。たとえば、x 変数をキャプチャするクロージャは次のようになります。
|val| val + x
クロージャの構文と機能は、その場で使用するのに非常に便利です。クロージャの呼び出しは、関数の呼び出しとまったく同じです。ただし、入力型と戻り値の型はどちらも推論_できます_が、入力変数名は指定_しなければなりません_。
クロージャのその他の特徴は次のとおりです。
- 入力変数を囲むのに
()の代わりに||を使用する。 - 1行の式では本体を区切る記号(
{})が任意である(それ以外の場合は必須)。 - 外側の環境変数をキャプチャできる。
fn main() { let outer_var = 42; // 通常の関数は、周囲の環境にある変数を参照できない //fn function(i: i32) -> i32 { i + outer_var } // TODO: 上の行のコメントを外して、コンパイラエラーを確認してください。コンパイラは // 代わりにクロージャを定義することを提案します。 // クロージャは無名であり、ここでは参照に束縛しています。 // アノテーションは関数のアノテーションと同じですが、省略可能です。 // 本体を囲む `{}` も同様に省略可能です。これらの名前のない関数は、 // 適切に名前付けされた変数に代入されます。 let closure_annotated = |i: i32| -> i32 { i + outer_var }; let closure_inferred = |i | i + outer_var ; // クロージャを呼び出す。 println!("closure_annotated: {}", closure_annotated(1)); println!("closure_inferred: {}", closure_inferred(1)); // クロージャの型が一度推論されると、別の型で再度推論することはできない。 //println!("別の型で closure_inferred を再利用することはできません: {}", closure_inferred(42i64)); // TODO: 上の行のコメントを外して、コンパイラエラーを確認してください。 // 引数を取らず、`i32` を返すクロージャ。 // 戻り値の型は推論される。 let one = || 1; println!("1を返すクロージャ: {}", one()); }
キャプチャ
クロージャは本質的に柔軟であり、アノテーションなしでクロージャを機能させるために必要なことを行います。これにより、キャプチャはユースケースに柔軟に適応でき、場合によってはムーブし、場合によっては借用します。 クロージャは変数をキャプチャできます。
- 参照によって:
&T - 可変参照によって:
&mut T - 値によって:
T
クロージャは変数を優先的に参照によってキャプチャし、必要な場合にのみ、より制約の強い方法へ移行します。
fn main() { use std::mem; let color = String::from("green"); // `color` を出力するクロージャ。これは即座に `color` を借用 (`&`) し、 // その借用とクロージャを `print` 変数に格納する。借用は // `print` が最後に使用されるまで継続する。 // // `println!` は引数を不変参照としてのみ必要とするため、 // それ以上に制約の強いものは課さない。 let print = || println!("`color`: {}", color); // 借用を使用してクロージャを呼び出す。 print(); // クロージャは `color` への不変参照だけを保持しているため、 // `color` は再び不変で借用できる。 let _reborrow = &color; print(); // `print` の最後の使用後は、ムーブまたは再借用が許可される let _color_moved = color; let mut count = 0; // `count` をインクリメントするクロージャは、`&mut count` または `count` の // どちらも受け取れるが、`&mut count` の方が制約が弱いため、それを受け取る。 // 即座に `count` を借用する。 // // 内部に `&mut` が格納されているため、`inc` には `mut` が必要。 // したがって、クロージャを呼び出すと `count` が変更されるため、`mut` が必要になる。 let mut inc = || { count += 1; println!("`count`: {}", count); }; // 可変借用を使用してクロージャを呼び出す。 inc(); // クロージャは後で呼び出されるため、まだ `count` を可変で借用している。 // 再借用しようとするとエラーになる。 // let _reborrow = &count; // ^ TODO: この行のコメントを外してみてください。 inc(); // クロージャはもはや `&mut count` を借用する必要がない。 // そのため、エラーなしで再借用できる let _count_reborrowed = &mut count; // 非コピー型。 let movable = Box::new(3); // `mem::drop` は `T` を必要とするため、これは値によって受け取る必要がある。 // コピー型であれば、元の値はそのまま残り、クロージャにコピーされる。 // 非コピー型はムーブする必要があるため、`movable` は即座に // クロージャへムーブする。 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!("vec には {} 個の要素があります", haystack.len()); // ^ 上の行のコメントを外すと、コンパイル時エラーになる // 借用チェッカーは、ムーブされた後の変数の再利用を許可しないため。 // クロージャのシグネチャから `move` を削除すると、クロージャは // _haystack_ 変数を不変で借用するため、_haystack_ はまだ利用可能であり、 // 上の行のコメントを外してもエラーにはならない。 }
関連項目:
入力パラメーターとして
Rust は、ほとんど型注釈なしで、その場で変数をどのようにキャプチャするかを選択しますが、関数を書く際にはこの曖昧さは許されません。 入力パラメーターとしてクロージャを受け取る場合、クロージャの完全な型は、いくつかの traits のいずれかを使用して注釈付けする必要があり、それらはクロージャがキャプチャした値に対して何を行うかによって決まります。 制約が強い順に、次のとおりです。
Fn: クロージャはキャプチャした値を参照(&T)で使用しますFnMut: クロージャはキャプチャした値を可変参照(&mut T)で使用しますFnOnce: クロージャはキャプチャした値を値(T)で使用します
コンパイラーは、変数ごとに、可能な限り最も制約の少ない方法で変数をキャプチャします。
たとえば、FnOnce と注釈付けされたパラメーターについて考えてみましょう。これは、クロージャが &T、&mut T、または T でキャプチャする_可能性がある_ことを指定しますが、最終的にはコンパイラーが、キャプチャされた変数がクロージャ内でどのように使用されるかに基づいて選択します。
これは、ムーブが可能であれば、どの種類の借用も可能であるはずだからです。逆は成り立たないことに注意してください。パラメーターが Fn と注釈付けされている場合、&mut T または T による変数のキャプチャは許可されません。ただし、&T は許可されます。
次の例では、Fn、FnMut、FnOnce の使用を入れ替えて、何が起こるか試してみてください。
// クロージャを引数として受け取り、それを呼び出す関数。 // <F> は F が「ジェネリック型パラメーター」であることを示します fn apply<F>(f: F) where // クロージャは入力を受け取らず、何も返しません。 F: FnOnce() { // ^ TODO: これを `Fn` または `FnMut` に変更してみてください。 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(); // 2 つの変数をキャプチャします: `greeting` は参照で、 // `farewell` は値でキャプチャされます。 let diary = || { // `greeting` は参照です: `Fn` が必要です。 println!("I said {}.", greeting); // ミューテーションにより、`farewell` は // 可変参照でキャプチャされます。これで `FnMut` が必要になります。 farewell.push_str("!!!"); println!("Then I screamed {}.", farewell); println!("Now I can sleep. zzzzz"); // drop を手動で呼び出すと、`farewell` は // 値でキャプチャされます。これで `FnOnce` が必要になります。 mem::drop(farewell); }; // クロージャを適用する関数を呼び出します。 apply(diary); // `double` は `apply_to_3` のトレイト境界を満たします let double = |x| 2 * x; println!("3 doubled: {}", apply_to_3(double)); }
関連項目:
std::mem::drop、Fn、FnMut、ジェネリクス、where、および FnOnce
型の匿名性
クロージャは、外側のスコープから変数を簡潔にキャプチャします。これには何か 影響があるのでしょうか?もちろんあります。クロージャを関数の パラメータとして使用するにはジェネリクスが必要になることに注目してください。これは、 クロージャの定義方法に起因して必要です。
#![allow(unused)] fn main() { // `F` はジェネリックでなければなりません。 fn apply<F>(f: F) where F: FnOnce() { f(); } }
クロージャが定義されると、コンパイラはキャプチャされた変数を内部に格納するための 新しい匿名構造体を暗黙的に作成し、その一方で、この未知の型に対して traits のいずれかである Fn、FnMut、または FnOnce によって機能を 実装します。この型は変数に割り当てられ、呼び出されるまで格納されます。
この新しい型は未知の型であるため、関数内で使用するにはすべて ジェネリクスが必要になります。しかし、境界のない型パラメータ <T> では依然として曖昧であり、 許可されません。したがって、それが実装している traits のいずれかである 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); }
関連項目:
入力関数
クロージャは引数として使用できるため、関数についても同じことが言えるのではないかと思うかもしれません。 そして実際、その通りです!クロージャをパラメータとして受け取る関数を宣言した場合、 そのクロージャのトレイト境界を満たす任意の関数をパラメータとして渡すことができます。
// ジェネリックな `F` 引数を取り、`Fn` で境界付けして、 // それを呼び出す関数を定義する 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 traits は、 クロージャが外側のスコープから変数をどのようにキャプチャするかを規定します。
関連項目:
出力パラメーターとして
クロージャを入力パラメーターとして使うことが可能なら、クロージャを 出力パラメーターとして返すことも可能なはずです。しかし、無名 クロージャ型は定義上不明であるため、それらを返すには impl Trait を使用する必要があります。
クロージャを返すために有効なトレイトは次のとおりです。
FnFnMutFnOnce
これに加えて、move キーワードを使用する必要があります。これは、すべてのキャプチャが 値によって行われることを示します。これが必要なのは、参照によるキャプチャがあると、 関数が終了した時点でそれらがドロップされ、クロージャ内に無効な参照が 残ってしまうためです。
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(); }
関連項目:
Fn、FnMut、ジェネリクス、および impl Trait。
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` はクロージャのパラメータ型で、
// イテレータによって決まります(例: `.iter()` では `&T`、
// `.into_iter()` では `T`)。
F: FnMut(Self::Item) -> bool;
}
fn main() { let vec1 = vec![1, 2, 3]; let vec2 = vec![4, 5, 6]; // vec の `iter()` は `&i32` を生成します。`i32` に分解します。 println!("2 in vec1: {}", vec1.iter() .any(|&x| x == 2)); // vec の `into_iter()` は `i32` を生成します。分解は不要です。 println!("2 in vec2: {}", vec2.into_iter().any(|x| x == 2)); // `iter()` は `vec1` とその要素を借用するだけなので、再び使用できます println!("vec1 len: {}", vec1.len()); println!("First element of vec1 is: {}", vec1[0]); // `into_iter()` は `vec2` とその要素をムーブするため、再び使用することはできません // println!("First element of vec2 is: {}", vec2[0]); // println!("vec2 len: {}", vec2.len()); // TODO: 上の 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]; // `vec1.iter()` は `&i32` を生成する。 let mut iter = vec1.iter(); // `vec2.into_iter()` は `i32` を生成する。 let mut into_iter = vec2.into_iter(); // `iter()` は `&i32` を生成し、`find` は `&Item` を述語に渡す。 // `Item = &i32` なので、クロージャ引数の型は `&&i32` になり、 // これをパターンマッチして `i32` までデリファレンスする。 println!("vec1 で 2 を検索: {:?}", iter.find(|&&x| x == 2)); // `into_iter()` は `i32` を生成し、`find` は `&Item` を述語に渡す。 // `Item = i32` なので、クロージャ引数の型は `&i32` になり、 // これをパターンマッチして `i32` までデリファレンスする。 println!("vec2 で 2 を検索: {:?}", into_iter.find(|&x| x == 2)); let array1 = [1, 2, 3]; let array2 = [4, 5, 6]; // `array1.iter()` は `&i32` を生成し、`find` は `&Item` を // 述語に渡す。`Item = &i32` なので、クロージャ引数の型は `&&i32` になる。 println!("array1 で 2 を検索: {:?}", array1.iter().find(|&&x| x == 2)); // `array2.into_iter()` は(Rust 2021 edition 以降)`i32` を生成し、 // `find` は `&Item` を述語に渡す。`Item = i32` なので、 // クロージャ引数の型は `&i32` になる。 println!("array2 で 2 を検索: {:?}", array2.into_iter().find(|&x| x == 2)); }
Iterator::find はアイテムへの参照を返します。しかし、アイテムの_インデックス_が必要な場合は、Iterator::position を使用します。
fn main() { let vec = vec![1, 9, 3, 3, 13, 2]; // `position` はイテレータの `Item` を値で述語に渡す。 // `vec.iter()` は `&i32` を生成するため、述語は `&i32` を受け取り、 // これをパターンマッチして `i32` にデリファレンスする。 let index_of_first_even_number = vec.iter().position(|&x| x % 2 == 0); assert_eq!(index_of_first_even_number, Some(5)); // `vec.into_iter()` は `i32` を生成するため、述語は `i32` を直接受け取る。 let index_of_first_negative_number = vec.into_iter().position(|x| x < 0); assert_eq!(index_of_first_negative_number, None); }
関連項目:
std::iter::Iterator::rposition
高階関数
Rust は高階関数 (HOF) を提供します。これらは、1 つ以上の関数を受け取ったり、より有用な関数を生成したりする関数です。HOF と遅延イテレータは、Rust に関数型の風味を与えます。
fn is_odd(n: u32) -> bool { n % 2 == 1 } fn main() { println!("二乗が1000未満かつ奇数であるすべての数の合計を求める"); let upper = 1000; // 命令型アプローチ // アキュムレータ変数を宣言 let mut acc = 0; // 反復処理: 0, 1, 2, ... から無限大まで for n in 0.. { // 数を二乗する let n_squared = n * n; if n_squared >= upper { // 上限を超えた場合はループを抜ける break; } else if is_odd(n_squared) { // 奇数なら値を累積する acc += n; } } println!("命令型スタイル: {}", acc); // 関数型アプローチ let sum: u32 = (0..).take_while(|&n| n * n < upper) // 上限未満 .filter(|&n| is_odd(n * n)) // 奇数のもの .sum(); // それらを合計する println!("関数型スタイル: {}", sum); }
Option および Iterator はそれぞれ相当数の HOF を実装しています。
発散関数
発散関数は決して戻りません。これらは空の型である ! を使って示されます。
#![allow(unused)] fn main() { fn foo() -> ! { panic!("この呼び出しは決して戻りません。"); } }
他のすべての型とは異なり、この型はインスタンス化できません。なぜなら、この型が取り得るすべての可能な値の集合は空だからです。これは、取り得る値が正確に 1 つだけある () 型とは異なることに注意してください。
たとえば、この関数は通常どおり戻りますが、戻り値には情報がありません。
fn some_fn() { () } fn main() { let _a: () = some_fn(); println!("この関数は戻るので、この行を見ることができます。"); }
これに対して、次の関数は呼び出し元に制御を戻すことは決してありません。
#![feature(never_type)]
fn main() {
let x: ! = panic!("この呼び出しは決して戻りません。");
println!("この行を見ることは決してありません!");
}
これは抽象的な概念のように思えるかもしれませんが、実際には非常に有用で、しばしば便利です。この型の主な利点は、他の任意の型にキャストできることであり、match の分岐のように正確な型が必要とされる状況で汎用的に使えます。この柔軟性により、次のようなコードを書くことができます。
fn main() { fn sum_odd_numbers(up_to: u32) -> u32 { let mut acc = 0; for i in 0..up_to { // この match 式の戻り値の型は u32 でなければならないことに注意してください // これは "addition" 変数の型によるものです。 let addition: u32 = match i%2 == 1 { // "i" 変数は u32 型であり、これはまったく問題ありません。 true => i, // 一方、"continue" 式は u32 を返しませんが、 // 決して戻らないため、依然として問題ありません。したがって // match 式の型要件に違反しません。 false => continue, }; acc += addition; } acc } println!("9 未満(9 を含まない)の奇数の合計: {}", sum_odd_numbers(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)` 構文を使って宣言された関数は、 // 指定されたパス内でのみ可視です。`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 path) アイテムは、指定されたモジュール内からのみ呼び出せます // エラー!関数 `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` のプライベートフィールドを持つパブリック構造体 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: "公開情報" }; // そして、そのフィールドには通常どおりアクセスできます。 println!("開いた箱の中身: {}", open_box.contents); // プライベートフィールドを持つパブリック構造体は、フィールド名を使って構築できません。 // エラー!`ClosedBox` にはプライベートフィールドがあります //let closed_box = my::ClosedBox { contents: "機密情報" }; // TODO ^ この行をコメント解除してみましょう // ただし、プライベートフィールドを持つ構造体は // パブリックなコンストラクタを使って作成できます let _closed_box = my::ClosedBox::new("機密情報"); // また、パブリック構造体のプライベートフィールドにはアクセスできません。 // エラー!`contents` フィールドはプライベートです //println!("閉じた箱の中身: {}", _closed_box.contents); // TODO ^ この行をコメント解除してみましょう }
関連項目:
use 宣言
use 宣言は、アクセスを容易にするために、フルパスを新しい名前にバインドするために使用できます。多くの場合、次のように使用されます。
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; // `use` バインディングはローカルスコープを持ちます。この場合、 // `function()` のシャドーイングはこのブロック内だけです。 function(); println!("Leaving block"); } function(); }
pub use を使用して、モジュールから項目を再エクスポートすることもできます。これにより、その項目にはモジュールの公開インターフェースを通じてアクセスできます。
mod deeply { pub mod nested { pub fn function() { println!("called `deeply::nested::function()`"); } } } mod cool { pub use crate::deeply::nested::function; } fn main() { cool::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(); // `my` 内の別のモジュールにアクセスするために `self` を使用することもできます。 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
│ └── nested.rs
├── my.rs
└── split.rs
split.rs では:
// この宣言は `my.rs` という名前のファイルを探し、
// その内容をこのスコープ配下の `my` という名前のモジュール内に挿入します
mod my;
fn function() {
println!("called `function()`");
}
fn main() {
my::function();
function();
my::indirect_access();
my::nested::function();
}
my.rs では:
// 同様に、`mod inaccessible` と `mod nested` は
// `inaccessible.rs` ファイルと `nested.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 宣言が含まれている場合、コンパイラを実行する_前に_、モジュールファイルの内容が クレートファイル内で mod 宣言が見つかった場所に挿入されます。言い換えると、 モジュールは個別にはコンパイルされ_ず_、クレートだけがコンパイルされます。
クレートはバイナリまたはライブラリにコンパイルできます。デフォルトでは、rustc はクレートからバイナリを生成します。この動作は、--crate-type フラグに lib を渡すことで上書きできます。
ライブラリの作成
ライブラリを作成し、それを別のクレートにリンクする方法を見てみましょう。
rary.rs では:
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" という接頭辞が付き、デフォルトではクレートファイルにちなんだ名前が付けられますが、このデフォルト名は、rustc に --crate-name オプションを渡すか、crate_name 属性を使用することで上書きできます。
ライブラリを使用する
この新しいライブラリにクレートをリンクするには、rustc の --extern フラグを使用できます。その すべてのアイテムは、ライブラリと同じ名前のモジュールの下にインポートされます。 このモジュールは通常、他のモジュールと同じように動作します。
// extern crate rary; // Rust 2015 エディション以前では必要になる場合があります
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 パッケージ管理ツールです。コード品質と開発速度を向上させる、本当に 便利な機能が数多くあります!これには次のものが含まれます
- 依存関係の管理と crates.io(公式の Rust パッケージレジストリ)との統合
- 単体テストの認識
- ベンチマークの認識
この章では簡単な基本事項をいくつか説明しますが、包括的なドキュメントは The Cargo Book にあります。
依存関係
ほとんどのプログラムは何らかのライブラリに依存しています。依存関係を手作業で管理したことがあれば、それがどれほど面倒かご存じでしょう。幸いなことに、Rust エコシステムには標準で cargo が備わっています!cargo はプロジェクトの依存関係を管理できます。
新しい Rust プロジェクトを作成するには、次のようにします。
# バイナリ
cargo new foo
# ライブラリ
cargo new --lib bar
この章の残りでは、ライブラリではなくバイナリを作っていると仮定しますが、概念はすべて同じです。
上記のコマンドを実行すると、次のようなファイル階層が表示されるはずです。
.
├── bar
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
└── foo
├── Cargo.toml
└── src
└── main.rs
main.rs は新しい foo プロジェクトのルートソースファイルです。ここに新しい点はありません。 Cargo.toml は、このプロジェクトにおける cargo の設定ファイルです。中を見ると、次のような内容が表示されるはずです。
[package]
name = "foo"
version = "0.1.0"
authors = ["mark"]
[dependencies]
[package] の下にある name フィールドは、プロジェクトの名前を決定します。これはクレートを公開する場合に crates.io で使用されます(詳しくは後述します)。また、コンパイル時の出力バイナリの名前にもなります。
version フィールドは、セマンティックバージョニングを使用したクレートのバージョン番号です。
authors フィールドは、クレートを公開するときに使用される作者の一覧です。
[dependencies] セクションでは、プロジェクトの依存関係を追加できます。
たとえば、プログラムに優れた CLI を持たせたいとします。crates.io(公式の Rust パッケージレジストリ)には、優れたパッケージがたくさんあります。人気のある選択肢の 1 つが clap です。本稿執筆時点で、公開されている clap の最新バージョンは 2.27.1 です。プログラムに依存関係を追加するには、Cargo.toml の [dependencies] の下に次を追加するだけです: clap = "2.27.1"。これで完了です!プログラム内で 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 のフォーマット仕様に記載されています。
プロジェクトをビルドするには、プロジェクトディレクトリ内の任意の場所(サブディレクトリを含みます!)で cargo build を実行できます。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 にこのバイナリだけをコンパイルまたは実行するよう伝えるには、cargo に --bin my_other_bin フラグを渡すだけです。ここで my_other_bin は、扱いたいバイナリの名前です。
追加のバイナリに加えて、cargo はベンチマーク、テスト、サンプルなどの その他の機能をサポートしています。
次の章では、テストについてより詳しく見ていきます。
テスト
ご存じのとおり、テストはあらゆるソフトウェアに不可欠です!Rust はユニットテストとインテグレーションテストをファーストクラスでサポートしています(TRPL のこの章を参照)。
上記でリンクされているテストの章から、ユニットテストとインテグレーションテストの書き方がわかります。構成としては、ユニットテストはテスト対象のモジュール内に配置でき、インテグレーションテストは専用の tests/ ディレクトリに配置できます。
foo
├── Cargo.toml
├── src
│ └── main.rs
│ └── lib.rs
└── tests
├── my_test.rs
└── my_other_test.rs
tests 内の各ファイルは個別のインテグレーションテストです。つまり、依存クレートから呼び出されるかのようにライブラリをテストすることを目的としたテストです。
テストの章では、3 つの異なるテストスタイルであるユニット、ドキュメント、インテグレーションについて詳しく説明しています。
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 4 tests
test test_bar ... ok
test test_baz ... ok
test test_foo_bar ... ok
test test_foo ... ok
test result: ok. 4 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
注意点が 1 つあります。Cargo は複数のテストを並行して実行する場合があるため、テスト同士が競合しないようにしてください。
この並行性が問題を引き起こす例の 1 つは、以下のように 2 つのテストが 1 つのファイルに出力する場合です。
#![allow(unused)] fn main() { #[cfg(test)] mod tests { // 必要なモジュールをインポートする use std::fs::OpenOptions; use std::io::Write; // このテストはファイルに書き込む #[test] fn test_file() { // ファイル ferris.txt を開く。存在しない場合は作成する。 let mut file = OpenOptions::new() .append(true) .create(true) .open("ferris.txt") .expect("Failed to open ferris.txt"); // "Ferris" を 5 回出力する。 for _ in 0..5 { file.write_all("Ferris\n".as_bytes()) .expect("Could not write to ferris.txt"); } } // このテストは同じファイルに書き込もうとする #[test] fn test_file_also() { // ファイル ferris.txt を開く。存在しない場合は作成する。 let mut file = OpenOptions::new() .append(true) .create(true) .open("ferris.txt") .expect("Failed to open ferris.txt"); // "Corro" を 5 回出力する。 for _ in 0..5 { file.write_all("Corro\n".as_bytes()) .expect("Could not write to ferris.txt"); } } } }
意図としては次の結果を得ることです。
$ cat ferris.txt
Ferris
Ferris
Ferris
Ferris
Ferris
Corro
Corro
Corro
Corro
Corro
実際に ferris.txt に書き込まれるのは次の内容です。
$ cargo test test_file && cat ferris.txt
Corro
Ferris
Corro
Ferris
Corro
Ferris
Corro
Ferris
Corro
Ferris
ビルドスクリプト
cargo による通常のビルドだけでは不十分な場合があります。たとえば、あなたのクレートが cargo で正常にコンパイルされる前に、コード生成や、コンパイルが必要なネイティブコードなど、何らかの前提条件を必要とする場合です。この問題を解決するために、Cargo が実行できるビルドスクリプトがあります。
パッケージにビルドスクリプトを追加するには、次のように Cargo.toml で指定できます。
[package]
...
build = "build.rs"
それ以外の場合、Cargo はデフォルトでプロジェクトディレクトリ内の build.rs ファイルを探します。
ビルドスクリプトの使い方
ビルドスクリプトは、パッケージ内の他のものをコンパイルする前にコンパイルされ、呼び出される、単なる別の Rust ファイルです。そのため、クレートの前提条件を満たすために使用できます。
Cargo は、こちらで指定されている環境変数を介して、スクリプトに入力を提供します。
スクリプトは stdout 経由で出力を提供します。出力されたすべての行は target/debug/build/<pkg>/output に書き込まれます。さらに、cargo: で始まる行は Cargo によって直接解釈されるため、パッケージのコンパイル用パラメーターを定義するために使用できます。
さらに詳しい仕様と例については、Cargo 仕様を参照してください。
属性
属性は、何らかのモジュール、クレート、またはアイテムに適用されるメタデータです。このメタデータは、次の用途に使用できます。
- コードの条件付きコンパイル
- クレート名、バージョン、種類(バイナリまたはライブラリ)の設定
- リント(警告)の無効化
- コンパイラ機能(マクロ、グロブインポートなど)の有効化
- 外部ライブラリへのリンク
- 関数をユニットテストとしてマーク
- ベンチマークの一部となる関数をマーク
- 属性風マクロ
属性は #[outer_attribute] または #![inner_attribute] のような形式で、 両者の違いは適用される場所です。
-
#[outer_attribute]は、その直後に続くアイテムに適用されます。 アイテムの例としては、関数、モジュール宣言、定数、構造体、enum などがあります。 次の例では、属性#[derive(Debug)]が構造体Rectangleに適用されています。#![allow(unused)] fn main() { #[derive(Debug)] struct Rectangle { width: u32, height: u32, } } -
#![inner_attribute]は、それを囲むアイテム(通常はモジュールまたはクレート)に適用されます。 言い換えると、この属性は、それが配置されたスコープ全体に適用されるものとして解釈されます。 次の例では、#![allow(unused_variables)]が(main.rsに配置されている場合) クレート全体に適用されています。#![allow(unused_variables)] fn main() { let x = 3; // これは通常、未使用の変数に関する警告を出します。 }
属性は、異なる構文で引数を取ることができます。
#[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 属性はクレートの名前を設定するために使用できます。
ただし、Rust のパッケージマネージャーである Cargo を使用する場合、crate_type 属性と crate_name 属性のどちらもまったく効果がないことに注意することが重要です。Rust プロジェクトの大半では Cargo が使用されるため、これは crate_type と crate_name の実際の利用が比較的限定的であることを意味します。
// このクレートはライブラリです #![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 リテラルに評価され、実行時のチェックを可能にします。どちらも 同一の引数構文を利用します。
cfg! は #[cfg] とは異なり、コードを一切削除せず、true または false に評価されるだけです。たとえば、cfg! が条件に使用されている場合、cfg! が何に評価されるかに関係なく、if/else 式内のすべてのブロックが有効である必要があります。
// この関数は、ターゲット OS が linux の場合にのみコンパイルされます #[cfg(target_os = "linux")] fn are_you_on_linux() { println!("linux で実行しています!"); } // そして、この関数は、ターゲット OS が linux で*ない*場合にのみコンパイルされます #[cfg(not(target_os = "linux"))] fn are_you_on_linux() { println!("linux で実行して*いません*!"); } fn main() { are_you_on_linux(); println!("本当ですか?"); if cfg!(target_os = "linux") { println!("はい。間違いなく linux です!"); } else { println!("はい。間違いなく linux では*ありません*!"); } }
関連項目:
カスタム
target_os のような一部の条件は rustc によって暗黙的に提供されますが、 カスタム条件は --cfg フラグを使って rustc に渡す必要があります。
#[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 では、"ジェネリック" は 1 つ以上のジェネリック型パラメーター <T> を受け取るものすべてを表す言葉でもあります。ジェネリック型パラメーターとして指定された型はすべてジェネリックであり、それ以外はすべて具象(非ジェネリック)です。
たとえば、任意の型の引数 T を受け取る foo という名前の_ジェネリック関数_を定義します。
fn foo<T>(arg: T) { ... }
T は <T> を使ってジェネリック型パラメーターとして指定されているため、ここで (arg: T) として使われるとき、ジェネリックと見なされます。これは、たとえ T が以前に struct として定義されていたとしても同様です。
この例は、構文の一部が実際に使われている様子を示しています。
// 具象型 `A`。 struct A; // 型 `Single` を定義する際、`A` の最初の使用の前には `<A>` がありません。 // したがって、`Single` は具象型であり、`A` は上で定義されたものです。 struct Single(A); // ^ ここが `Single` における型 `A` の最初の使用です。 // ここでは、`T` の最初の使用の前に `<T>` があるため、`SingleGen` はジェネリック型です。 // 型パラメーター `T` はジェネリックなので、最上部で定義された具象型 `A` を含め、 // 何にでもなり得ます。 struct SingleGen<T>(T); 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); // 最上部で定義された `A` を使います。 let _i32 = SingleGen(6); // `i32` を使います。 let _char = SingleGen('a'); // `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<T>` の引数 `_s` を取る関数 `gen_spec_t` を定義します。 // 型パラメータ `A` が明示的に与えられていますが、`A` は // `gen_spec_t` のジェネリック型パラメータとして指定されていないため、ジェネリックではありません。 fn gen_spec_t(_s: SGen<A>) {} // 型 `SGen<i32>` の引数 `_s` を取る関数 `gen_spec_i32` を定義します。 // 特定の型である型パラメータ `i32` が明示的に与えられています。 // `i32` はジェネリック型ではないため、この関数もジェネリックではありません。 fn gen_spec_i32(_s: SGen<i32>) {} // 型 `SGen<T>` の引数 `_s` を取る関数 `generic` を定義します。 // `SGen<T>` の前に `<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`。 // `generic()` に型パラメータ `char` を明示的に指定します。 generic::<char>(SGen('a')); // `generic()` に型パラメータ `char` を暗黙的に指定します。 generic(SGen('c')); }
関連項目:
実装
関数と同様に、実装をジェネリックなままにするには注意が必要です。
#![allow(unused)] fn main() { struct S; // 具体型 `S` struct GenericVal<T>(T); // ジェネリック型 `GenericVal` // 型パラメータを明示的に指定する GenericVal の impl: impl GenericVal<f32> {} // `f32` を指定 impl GenericVal<S> {} // 上で定義した `S` を指定 // ジェネリックなままにするには、型の前に `<T>` が必要 impl<T> GenericVal<T> {} }
struct Val { val: f64, } struct GenVal<T> { gen_val: T, } // Val の impl impl Val { fn value(&self) -> &f64 { &self.val } } // ジェネリック型 `T` に対する GenVal の impl 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()); }
関連項目:
トレイト
もちろん、trait もジェネリックにできます。ここでは、自身と入力を drop するジェネリックメソッドとして Drop trait を再実装するものを定義します。
// コピーできない型。 struct Empty; struct Null; // `T` に対してジェネリックなトレイト。 trait DoubleDrop<T> { // 呼び出し元の型にメソッドを定義する。このメソッドは // 追加の単一パラメーター `T` を受け取り、それに対して何もしない。 fn double_drop(self, _: T); } // 任意のジェネリックパラメーター `T` と // 呼び出し元 `U` に対して `DoubleDrop<T>` を実装する。 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: これらの行をアンコメントしてみましょう。 }
関連項目:
境界
ジェネリックを扱う際、型パラメータは、型がどの機能を実装しているかを規定するために、しばしばトレイトを_境界_として使用しなければなりません。たとえば、以下の例では出力するためにトレイト Display を使用しているため、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]);
境界のもう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(&rectangle)); //print_debug(&_triangle); //println!("面積: {}", area(&_triangle)); // ^ TODO: これらのコメントを外してみよう。 // | エラー: `Debug` と `HasArea` のどちらも実装していない。 }
追加の注記として、where 句も、場合によっては境界を適用するために使用でき、より表現しやすくできます。
関連項目:
テストケース: 空の境界
境界の仕組みによる帰結として、trait が何の機能も 含んでいない場合でも、それを境界として使用できます。Eq と Copy は、std ライブラリに含まれるそのような trait の例です。
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 { "赤" } fn blue<T: Blue>(_: &T) -> &'static str { "青" } fn main() { let cardinal = Cardinal; let blue_jay = BlueJay; let _turkey = Turkey; // 境界があるため、`red()` はアオカケスには機能せず、 // その逆も同様です。 println!("ショウジョウコウカンチョウは{}です", red(&cardinal)); println!("アオカケスは{}です", blue(&blue_jay)); //println!("七面鳥は{}です", red(&_turkey)); // ^ TODO: この行のコメントを解除してみてください。 }
関連項目:
std::cmp::Eq、std::marker::Copy、および trait
複数の境界
1つの型に対する複数の境界は、+ で適用できます。通常どおり、異なる型は , で区切ります。
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>: Debug` が必要です。 // それ以外にすると、誤った境界を使うことになります。 fn print_in_option(self) { println!("{:?}", Some(self)); } } fn main() { let vec = vec![1, 2, 3]; vec.print_in_option(); }
関連項目:
New Typeイディオム
newtypeイディオムは、正しい型の値がプログラムに渡されることをコンパイル時に保証します。
たとえば、マイル単位で距離を測定する関数には、Miles型の値を渡さなければなりません。
struct Miles(f64); struct Kilometers(f64); impl Miles { pub fn to_kilometers(&self) -> Kilometers { Kilometers(self.0 * 1.609344) } } impl Kilometers { pub fn to_miles(&self) -> Miles { Miles(self.0 / 1.609344) } } fn is_a_marathon(distance: &Miles) -> bool { distance.0 >= 26.2 } fn main() { let distance = Miles(30.0); let distance_km = distance.to_kilometers(); println!("Is a marathon? {}", is_a_marathon(&distance)); println!("Is a marathon? {}", is_a_marathon(&distance_km.to_miles())); // println!("Is a marathon? {}", is_a_marathon(&distance_km)); }
最後のprint文のコメントを解除すると、渡す型はMilesでなければならないことを確認できます。
newtypeの値を基底型として取得するには、次のようにタプル構文または分配束縛構文を使用できます。
struct Miles(f64); fn main() { let distance = Miles(42.0); let distance_as_primitive_1: f64 = distance.0; // タプル let Miles(distance_as_primitive_2) = distance; // 分配束縛 }
関連項目:
関連アイテム
「関連アイテム」とは、さまざまな型のitemに 関する一連の規則を指します。これはtraitのジェネリクスを拡張するものであり、 traitが内部で新しいアイテムを定義できるようにします。
そのようなアイテムの1つは_関連型_と呼ばれ、traitがそのコンテナー型に対して ジェネリックである場合に、より単純な使用パターンを提供します。
関連項目:
問題
コンテナ型に対してジェネリックな trait には、型指定の要件があります。trait のユーザーは、そのジェネリック型をすべて指定しなければなりません。
以下の例では、Contains trait はジェネリック型 A と B の使用を許可しています。その後、このトレイトは Container 型に対して実装され、A と B に i32 を指定することで、fn difference() で使用できるようにしています。
Contains はジェネリックであるため、fn difference() に対してジェネリック型を すべて 明示的に記述することを強制されます。実際には、A と B が 入力 C によって決定されることを表現する方法が必要です。次のセクションで見るように、関連型はまさにその機能を提供します。
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 { // 格納されている数値が等しい場合は真。 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!("コンテナは {} と {} を含んでいますか: {}", &number_1, &number_2, container.contains(&number_1, &number_2)); println!("最初の数値: {}", container.first()); println!("最後の数値: {}", container.last()); println!("差は: {}", difference(&container)); }
関連項目:
関連型
「関連型」を使用すると、内部の型を_出力_型としてトレイト内へローカルに移動することで、コード全体の可読性が向上します。trait 定義の構文は次のとおりです。
#![allow(unused)] fn main() { // `A` と `B` は、`type` キーワードによってトレイト内で定義されます。 // (注: この文脈での `type` は、エイリアスに使われる場合の `type` とは // 異なります)。 trait Contains { type A; type B; // これらの新しい型をジェネリックに参照するための更新された構文。 fn contains(&self, _: &Self::A, _: &Self::B) -> bool; } }
trait 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` がどの型であるかを指定します。`input` 型が // `Container(i32, i32)` である場合、`output` 型は // `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!("コンテナは {} と {} を含んでいますか: {}", &number_1, &number_2, container.contains(&number_1, &number_2)); println!("最初の数値: {}", container.first()); println!("最後の数値: {}", container.last()); println!("差は: {}", difference(&container)); }
ファントム型パラメーター
ファントム型パラメーターとは、実行時には現れないものの、 コンパイル時に静的に(かつ、それだけで)検査される型パラメーターです。
データ型は、追加のジェネリック型パラメーターを使用して、マーカーとして機能させたり、 コンパイル時に型検査を行ったりできます。これらの追加パラメーターは ストレージ値を保持せず、実行時の振る舞いも持ちません。
次の例では、std::marker::PhantomData とファントム型パラメーターの概念を組み合わせて、 異なるデータ型を含むタプルを作成します。
use std::marker::PhantomData; // 隠しパラメーター `B` を持ち、`A` に対してジェネリックなファントムタプル構造体。 #[derive(PartialEq)] // この型に対する等価性テストを許可する。 struct PhantomTuple<A, B>(A, PhantomData<B>); // 隠しパラメーター `B` を持ち、`A` に対してジェネリックなファントム型構造体。 #[derive(PartialEq)] // この型に対する等価性テストを許可する。 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 の結果: {}", // _tuple1 == _tuple2); // コンパイル時エラー!型が一致しないため、これらは比較できない: // println!("_struct1 == _struct2 の結果: {}", // _struct1 == _struct2); }
関連項目:
テストケース: 単位の明確化
単位変換に役立つ方法は、ファントム型パラメータを用いて Add を実装することで確認できます。以下では Add trait について見ていきます。
// この構造は `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` を消費せず、それらを `self` と `rhs` にコピーします。 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; }
関連項目:
借用 (&), 境界 (X: Y), enum, impl & self, オーバーロード, ref, トレイト (X for Y), および タプル構造体.
スコープ規則
スコープは、所有権、借用、ライフタイムにおいて重要な役割を果たします。 つまり、スコープはコンパイラに対して、借用がいつ有効であるか、 リソースをいつ解放できるか、変数がいつ作成または破棄されるかを示します。
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` は破棄され、メモリが解放される } // 遊びでたくさんの Box を作成する // 手動でメモリを解放する必要はない! 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 がドロップされます"); } } fn main() { let x = ToDrop; println!("ToDrop を作成しました!"); }
関連項目:
所有権とムーブ
変数は自分自身のリソースを解放する責任を負うため、 リソースは所有者を 1 つしか持てません。これにより、リソースが 複数回解放されることを防ぎます。すべての変数が リソースを所有しているわけではないことに注意してください(例: references)。
代入(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; // ボックスを*ムーブ*し、所有権(および可変性)を変更する let mut mutable_box = immutable_box; println!("mutable_box contains {}", mutable_box); // ボックスの内容を変更する *mutable_box = 4; println!("mutable_box now contains {}", mutable_box); }
部分的なムーブ
単一の変数のデストラクチャリング内では、by-move と by-reference のパターン束縛を同時に使用できます。これを行うと、 変数の 部分的なムーブ が発生します。つまり、変数の一部はムーブされ、 他の部分は残ります。このような場合、その親変数は以後、全体としては 使用できませんが、参照されただけで(ムーブされていない)部分は引き続き 使用できます。なお、Drop トレイトを実装している型からは 部分的にムーブできません。なぜなら、その drop メソッドが後でそれを 全体として使用するためです。
fn main() { #[derive(Debug)] struct Person { name: String, age: Box<u8>, } // エラー! `Drop` トレイトを実装している型からはムーブできない //impl Drop for Person { // fn drop(&mut self) { // println!("Dropping the person struct {:?}", self) // } //} // TODO ^ これらの行のコメントを外してみてください let person = Person { name: String::from("Alice"), age: Box::new(20), }; // `name` は person からムーブされるが、`age` は参照される let Person { name, ref age } = person; println!("The person's age is {}", age); println!("The person's name is {}", name); // エラー! 部分的にムーブされた値 `person` の借用: 部分的なムーブが発生する //println!("The person struct is {:?}", person); // `person` は使用できないが、`person.age` はムーブされていないため使用できる println!("The person's age from person struct is {}", person.age); }
(この例では、部分的なムーブを説明するために age 変数をヒープ上に格納しています。 上のコードで ref を削除すると、person.age の所有権が変数 age に ムーブされるため、エラーになります。Person.age がスタック上に格納されている場合、 age の定義は person.age からデータをムーブせずにコピーするため、 ref は不要です。)
関連項目:
借用
たいていの場合、データの所有権を取得せずにデータへアクセスしたいものです。 これを実現するために、Rust は_借用_メカニズムを使用します。 オブジェクトを値(T)で渡す代わりに、参照(&T)で渡すことができます。
コンパイラは(借用チェッカーを通じて)、参照が_常に_有効なオブジェクトを指していることを静的に保証します。 つまり、あるオブジェクトへの参照が存在している間、そのオブジェクトは破棄できません。
// この関数は box の所有権を取得して破棄する fn eat_box_i32(boxed_i32: Box<i32>) { println!("{} を含む box を破棄しています", boxed_i32); } // この関数は i32 を借用する fn borrow_i32(borrowed_i32: &i32) { println!("この整数は: {}", borrowed_i32); } fn main() { // ヒープ上に box 化された i32 を作成し、スタック上に i32 を作成する // 覚えておこう: 数値には可読性のために任意のアンダースコアを追加できる // 5_i32 は 5i32 と同じ let boxed_i32 = Box::new(5_i32); let stacked_i32 = 6_i32; // box の内容を借用する。所有権は取得されないため、 // 内容は再び借用できる。 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_i32` に所有権を譲渡し、破棄できる 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, } // この関数は本への参照を取ります fn borrow_book(book: &Book) { println!("I immutably borrowed {} - {} edition", book.title, book.year); } // この関数は可変な本への参照を取り、`year` を 2014 に変更します fn new_edition(book: &mut Book) { book.year = 2014; println!("I mutably borrowed {} - {} edition", book.title, book.year); } fn main() { // `immutabook` という名前の不変な Book を作成します 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 ^ この行をコメントアウトしてください }
関連項目:
エイリアシング
データは何度でも不変に借用できますが、不変に借用されている間は、 元のデータを可変に借用することはできません。一方で、一度に許可される 可変借用は 1つ だけです。元のデータは、可変参照が最後に使用された 後 にのみ、再び借用できます。
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 の座標は: ({}, {}, {})", borrowed_point.x, another_borrow.y, point.z); // エラー!`point` は現在不変として借用されているため、 // 可変として借用できません。 // let mutable_borrow = &mut point; // TODO ^ この行のコメントを外してみてください // 借用された値はここでもう一度使用されます println!("Point の座標は: ({}, {}, {})", 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 座標は {}", point.z); // TODO ^ この行のコメントを外してみてください // OK!可変参照は不変参照として `println!` に渡すことができます println!("Point の座標は: ({}, {}, {})", mutable_borrow.x, mutable_borrow.y, mutable_borrow.z); // 可変参照は以降のコードではもう使用されないため、 // 再借用できます let new_borrowed_point = &point; println!("Point の現在の座標は: ({}, {}, {})", 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` の両方を完全に囲んでいるため、 // `i` のライフタイムが最も長くなります。`borrow1` と `borrow2` は互いに交わらないため、 // `borrow2` と比較した `borrow1` の期間は関係ありません。 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` を持つ
クロージャと同様に、ライフタイムを使用するにはジェネリクスが必要です。さらに、このライフタイム構文は、foo のライフタイムが 'a のライフタイムを超えてはならないことを示します。型の明示的な注釈は、'a がすでに導入されている場合に &'a T という形式になります。
複数のライフタイムがある場合、構文は似ています。
foo<'a, 'b>
// `foo` はライフタイムパラメーター `'a` と `'b` を持つ
この場合、foo のライフタイムは 'a または 'b のいずれのライフタイムも超えることはできません。
明示的なライフタイム注釈の使用例を次に示します。
// `print_refs` は、それぞれ異なるライフタイム `'a` と `'b` を持つ // `i32` への参照を 2 つ受け取る。これら 2 つのライフタイムはどちらも、 // 少なくとも関数 `print_refs` と同じ長さでなければならない。 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` になる。 }
関連項目:
関数
elisionを無視すると、ライフタイムを持つ関数シグネチャにはいくつかの制約があります。
- すべての参照には、注釈付きのライフタイムがなければなりません。
- 返されるすべての参照は、入力と同じライフタイムを持つか、
staticでなければなりません。
さらに、入力なしで参照を返すことは、無効なデータへの参照を返すことになる場合は禁止される点に注意してください。次の例は、ライフタイムを持つ関数の有効な形式をいくつか示しています。
// 関数と少なくとも同じ長さだけ生存しなければならない、 // ライフタイム`'a`を持つ1つの入力参照。 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`を作成し、その後に // 参照を作成します。その後、スコープを抜けるとデータがドロップされ、 // 無効なデータへの参照が返されることになります。 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(); }
関連項目:
構造体
構造体におけるライフタイムの注釈も、関数の場合と同様です:
// `i32` への参照を保持する型 `Borrowed`。 // `i32` への参照は `Borrowed` よりも長く存続しなければなりません。 #[derive(Debug)] struct Borrowed<'a>(&'a i32); // 同様に、ここでは両方の参照がこの構造体よりも長く存続しなければなりません。 #[derive(Debug)] struct NamedBorrowed<'a> { x: &'a i32, y: &'a i32, } // `i32` またはそれへの参照のいずれかである列挙型。 #[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 は {:?} 内で借用されています", single); println!("x と y は {:?} 内で借用されています", double); println!("x は {:?} 内で借用されています", reference); println!("y は {:?} 内では借用されて*いません*", 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` は、`Ref` には分からない何らかのライフタイム `'a` を持つ // ジェネリック型 `T` への参照を含みます。`T` は、`T` 内のあらゆる // *参照* が `'a` より長く生存しなければならないように境界付けされています。 // さらに、`Ref` のライフタイムは `'a` を超えてはなりません。 // `Debug` トレイトを使って出力するジェネリック関数。 fn print<T>(t: T) where T: Debug { println!("`print`: t は {:?}", t); } // ここでは、`T` が `Debug` を実装し、`T` 内のすべての *参照* が // `'a` より長く生存する場合に、`T` への参照が取得されます。さらに、 // `'a` はこの関数より長く生存しなければなりません。 fn print_ref<'a, T>(t: &'a T) where T: Debug + 'a { println!("`print_ref`: t は {:?}", t); } fn main() { let x = 7; let ref_x = Ref(&x); print_ref(&ref_x); print(ref_x); }
関連項目:
ジェネリクス、ジェネリクスにおける境界、および ジェネリクスにおける複数の境界
型強制
より長いライフタイムは、通常は機能しないスコープ内で機能するように、 より短いライフタイムへ型強制できます。 これは、Rust コンパイラによる推論された型強制という形で現れ、 また、ライフタイムの差異を宣言する形でも現れます:
// ここでは、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!("積は {} です", multiply(&first, &second)); println!("{} が最初です", choose_first(&first, &second)); }; }
スタティックライフタイム
Rust には、予約済みのライフタイム名がいくつかあります。その 1 つが '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であるstringリテラルを作る。
各方法を示す次の例を見てください。
// `'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); }
'static 参照はプログラムの生存期間の_残り_の間だけ有効であればよいため、プログラムの実行中に作成できます。これを示すために、以下の例では Box::leak を使って 'static 参照を動的に作成しています。この場合、それは間違いなく全期間にわたって生存するわけではなく、リークした時点以降だけ生存します。
extern crate rand; use rand::Fill; fn random_vec() -> &'static [u64; 100] { let mut rng = rand::rng(); let mut boxed = Box::new([0; 100]); boxed.fill(&mut rng); Box::leak(boxed) } fn main() { let first: &'static [u64; 100] = random_vec(); let second: &'static [u64; 100] = random_vec(); assert_ne!(first, second) }
トレイト境界
トレイト境界としては、その型が非 static な参照を一切含まないことを意味します。例: 受け取る側は望むだけ長くその型を保持でき、それをドロップするまで無効になることはありません。
これは、所有されたデータは常に 'static ライフタイム境界を満たしますが、その所有されたデータへの参照は一般には満たさない、という意味であることを理解するのが重要です。
use std::fmt::Debug; fn print_it(input: impl Debug + 'static) { println!("'static value passed in is: {:?}", input); } fn main() { // i は所有されており、参照を含まないため、'static である: let i = 5; print_it(i); // おっと、&i は main() のスコープによって定義される // ライフタイムしか持たないため、'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
関連項目:
省略
一部のライフタイムパターンは非常に一般的であるため、借用チェッカーは 入力の手間を省き、可読性を向上させるために、それらを省略することを許可します。 これは省略として知られています。Rust に省略が存在するのは、これらのパターンが 一般的であるという理由だけです。
次のコードは、省略の例をいくつか示しています。省略のより包括的な 説明については、本の ライフタイム省略 を参照してください。
// `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)); }
関連項目:
トレイト
trait は、未知の型 Self に対して定義されたメソッドの集合です。 同じトレイト内で宣言された他のメソッドにアクセスできます。
トレイトは任意のデータ型に対して実装できます。以下の例では、 メソッドのグループである Animal を定義します。その後、Animal trait を Sheep データ型に対して実装し、Sheep で Animal のメソッドを 使用できるようにします。
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!("{} が {} と言う", self.name(), self.noise()); } } impl Sheep { fn is_naked(&self) -> bool { self.naked } fn shear(&mut self) { if self.is_naked() { // 実装側のメソッドは、実装側のトレイトメソッドを使用できます。 println!("{} はすでに裸です...", self.name()); } else { println!("{} が毛を刈られました!", self.name); self.naked = true; } } } // `Sheep` に対して `Animal` トレイトを実装します。 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() { "メェェェ?" } else { "メェェェ!" } } // デフォルトのトレイトメソッドはオーバーライドできます。 fn talk(&self) { // たとえば、少し静かな黙考を加えることができます。 println!("{} は少し間を置いた... {}", 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. Clone。コピーによって&TからTを作成します。Copy。型に「ムーブセマンティクス」ではなく「コピーセマンティクス」を与えます。Hash。&Tからハッシュを計算します。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!("ランダムに動物を選択しました。その鳴き声は {} です", animal.noise()); }
演算子オーバーロード
Rust では、多くの演算子をトレイトによってオーバーロードできます。つまり、一部の演算子は、 入力引数に基づいて異なるタスクを実行するために使用できます。これが可能なのは、 演算子がメソッド呼び出しの構文糖だからです。たとえば、a + b の + 演算子は (a.add(b) のように)add メソッドを呼び出します。この 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) が呼び出されました"); 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) が呼び出されました"); BarFoo } } fn main() { println!("Foo + Bar = {:?}", Foo + Bar); println!("Bar + Foo = {:?}", Bar + Foo); }
関連項目
ドロップ
Drop トレイトには drop というメソッドが 1 つだけあります。このメソッドは、オブジェクトがスコープを抜けるときに自動的に呼び出されます。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` されることは*ありません* }
より実用的な例として、不要になった一時ファイルを自動的にクリーンアップするために Drop トレイトを使用する方法を示します。
use std::fs::File; use std::path::PathBuf; struct TempFile { file: File, path: PathBuf, } impl TempFile { fn new(path: PathBuf) -> std::io::Result<Self> { // 注意: File::create() は既存のファイルを上書きします let file = File::create(&path)?; Ok(Self { file, path }) } } // TempFile がドロップされるとき: // 1. まず、カスタムの drop 実装が実行されます。この時点ではファイルはまだ開いていますが、 // パスを使ってファイルシステムから削除できます。 // 2. 次に、drop から戻った後、Rust は各フィールドを自動的にドロップするため、 // File の drop が実行され、ファイルハンドルが閉じられます。 impl Drop for TempFile { fn drop(&mut self) { // 注意: ここでは File はまだ開いています — フィールドのデストラクタはこのメソッドの後に実行されます。 if let Err(e) = std::fs::remove_file(&self.path) { eprintln!("Failed to remove temporary file: {}", e); } println!("> Dropped temporary file: {:?}", self.path); // このメソッドが戻った後、Rust は各フィールド(`file` を含む)をドロップし、 // それによって基礎となるファイルハンドルが閉じられます。 } } fn main() -> std::io::Result<()> { // drop の動作を示すために新しいスコープを作成します { let temp = TempFile::new("test.txt".into())?; println!("Temporary file created"); // temp がスコープを抜けると、ファイルは自動的にクリーンアップされます } println!("End of scope - file should be cleaned up"); // 必要であれば手動でドロップすることもできます let temp2 = TempFile::new("another_test.txt".into())?; drop(temp2); // ファイルを明示的にドロップします println!("Manually dropped file"); Ok(()) }
イテレーター
Iterator トレイトは、配列などのコレクションに対するイテレーターを実装するために使用されます。
このトレイトで定義が必要なのは、next 要素に対するメソッドだけです。 これは impl ブロック内で手動で定義することも、配列や範囲のように自動的に定義することもできます。
一般的な状況で便利に使えるように、for 構文は .into_iter() メソッドを使用して、一部のコレクションをイテレーターに変換します。
struct Fibonacci { curr: u32, next: u32, } // `Fibonacci` に対して `Iterator` を実装します。 // `Iterator` トレイトで必要なのは、`next` 要素に対して定義されたメソッドと、 // イテレーターの戻り値の型を宣言する `associated type` だけです。 impl Iterator for Fibonacci { // この型は Self::Item を使って参照できます type Item = u32; // ここでは、`.curr` と `.next` を使ってシーケンスを定義します。 // 戻り値の型は `Option<T>` です: // * `Iterator` が終了した場合、`None` が返されます。 // * それ以外の場合、次の値は `Some` に包まれて返されます。 // 戻り値の型で Self::Item を使用しているため、関数シグネチャを更新することなく // 型を変更できます。 fn next(&mut self) -> Option<Self::Item> { let current = self.curr; self.curr = self.next; self.next = current + self.next; // フィボナッチ数列には終端がないため、この `Iterator` は // 決して `None` を返さず、常に `Some` が返されます。 Some(current) } } // フィボナッチ数列ジェネレーターを返します fn fibonacci() -> Fibonacci { Fibonacci { curr: 0, next: 1 } } fn main() { // `0..3` は 0、1、2 を生成する `Iterator` です。 let mut sequence = 0..3; println!("Four consecutive `next` calls on 0..3"); println!("> {:?}", sequence.next()); println!("> {:?}", sequence.next()); println!("> {:?}", sequence.next()); println!("> {:?}", sequence.next()); // `for` は `Iterator` が `None` を返すまで処理します。 // 各 `Some` の値はアンラップされ、変数(ここでは `i`)に束縛されます。 println!("Iterate through 0..3 using `for`"); for i in 0..3 { println!("> {}", i); } // `take(n)` メソッドは、`Iterator` を先頭の `n` 項に減らします。 println!("The first four terms of the Fibonacci sequence are: "); for i in fibonacci().take(4) { println!("> {}", i); } // `skip(n)` メソッドは、先頭の `n` 項を捨てることで `Iterator` を短くします。 println!("The next four terms of the Fibonacci sequence are: "); for i in fibonacci().skip(4).take(4) { println!("> {}", i); } let array = [1u32, 3, 3, 7]; // `iter` メソッドは、配列/スライスに対する `Iterator` を生成します。 println!("Iterate the following array {:?}", &array); for i in array.iter() { println!("> {}", i); } }
impl Trait
impl Trait は、次の 2 つの場所で使用できます。
- 引数型として
- 戻り値の型として
引数型として
関数がトレイトに対してジェネリックであるものの、具体的な型を気にしない場合は、引数の型として impl Trait を使用することで関数宣言を簡略化できます。
たとえば、次のコードを考えてみましょう。
fn parse_csv_document<R: std::io::BufRead>(src: R) -> std::io::Result<Vec<Vec<String>>> { src.lines() .map(|line| { // ソース内の各行について line.map(|line| { // 行を正常に読み取れた場合は処理し、そうでなければエラーを返す line.split(',') // カンマで区切られた行を分割する .map(|entry| String::from(entry.trim())) // 先頭と末尾の空白を削除する .collect() // 行内のすべての文字列を Vec<String> に収集する }) }) .collect() // すべての行を Vec<Vec<String>> に収集する }
parse_csv_document はジェネリックであり、BufReader<File> や [u8] など、BufRead を実装する任意の型を受け取ることができます。 しかし、R がどの型であるかは重要ではなく、R は src の型を宣言するためだけに使用されているため、この関数は次のようにも書けます。
fn parse_csv_document(src: impl std::io::BufRead) -> std::io::Result<Vec<Vec<String>>> { src.lines() .map(|line| { // ソース内の各行について line.map(|line| { // 行を正常に読み取れた場合は処理し、そうでなければエラーを返す line.split(',') // カンマで区切られた行を分割する .map(|entry| String::from(entry.trim())) // 先頭と末尾の空白を削除する .collect() // 行内のすべての文字列を Vec<String> に収集する }) }) .collect() // すべての行を Vec<Vec<String>> に収集する }
引数型として impl Trait を使用すると、関数のどの形式を使用するかを明示的に指定できないことに注意してください。つまり、2 つ目の例では parse_csv_document::<std::io::Empty>(std::io::empty()) は機能しません。
戻り値の型として
関数が 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) } fn main() { let singles = vec![-3, -2, 2, 3]; let doubles = double_positives(&singles); assert_eq!(doubles.collect::<Vec<i32>>(), vec![4, 6]); }
Clone と Copy
リソースを扱う場合、デフォルトの動作では、代入や関数呼び出しの際に リソースが転送されます。しかし、場合によってはリソースの コピーも作成する必要があります。
Clone トレイトは、まさにこれを行うために役立ちます。最も一般的には、 Clone トレイトで定義されている .clone() メソッドを使用できます。
Copy: 暗黙的なクローン
Copy トレイトにより、追加のロジックを必要とせず、単にビットをコピーするだけで 型を複製できます。型が Copy を実装している場合、代入や関数呼び出しでは、 値をムーブする代わりに暗黙的にコピーします。
重要: Copy には Clone が必要です。Copy を実装する型はすべて、 Clone も実装しなければなりません。これは、Copy がサブトレイトとして定義されているためです: trait Copy: Clone {}。Copy 型に対する Clone 実装は、単に ビットをコピーします。
すべての型が Copy を実装できるわけではありません。型が Copy になれるのは、次の場合のみです:
- そのすべての構成要素が
Copyである - 外部リソース(ヒープメモリ、ファイルハンドルなど)を管理しない
// リソースを持たないユニット構造体 // 注: Copy には Clone が必要なので、両方を derive する必要がある #[derive(Debug, Clone, Copy)] struct Unit; // `Clone` トレイトを実装する、リソースを持つタプル構造体 // Box<T> は Copy ではないため、これは Copy にはなれない #[derive(Clone, Debug)] struct Pair(Box<i32>, Box<i32>); fn main() { // `Unit` をインスタンス化する let unit = Unit; // `Unit` をコピーする - これはムーブではなく暗黙的なコピー! // Unit は Copy を実装しているため、値は自動的に複製される 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` にムーブし、リソースを移動する // Pair は Copy を実装していないため、これはムーブである let moved_pair = pair; println!("moved: {:?}", moved_pair); // エラー!`pair` はリソースを失っている //println!("original: {:?}", pair); // TODO ^ この行のコメント解除を試してみてください // `moved_pair` を `cloned_pair` にクローンする(リソースも含まれる) // Copy とは異なり、Clone は明示的である - .clone() を呼び出す必要がある let cloned_pair = moved_pair.clone(); // std::mem::drop を使用して、ムーブされた元のペアをドロップする drop(moved_pair); // エラー!`moved_pair` はドロップされている //println!("moved and dropped: {:?}", moved_pair); // TODO ^ この行のコメント解除を試してみてください // .clone() の結果は引き続き使用できる! println!("clone: {:?}", cloned_pair); }
スーパートレイト
Rust には「継承」はありませんが、あるトレイトを別のトレイトの上位集合として定義できます。例:
trait Person { fn name(&self) -> String; } // Person は Student のスーパートレイトです。 // Student を実装するには、Person も impl する必要があります。 trait Student: Person { fn university(&self) -> String; } trait Programmer { fn fav_language(&self) -> String; } // CompSciStudent(コンピューターサイエンスの学生)は Programmer と Student の両方のサブトレイトです。 // CompSciStudent を実装するには、両方のスーパートレイトを impl する必要があります。 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 favorite language is {}. My Git username is {}", student.name(), student.university(), student.fav_language(), student.git_username() ) } struct CSStudent { name: String, university: String, fav_language: String, git_username: String } impl Programmer for CSStudent { fn fav_language(&self) -> String { self.fav_language.clone() } } impl Student for CSStudent { fn university(&self) -> String { self.university.clone() } } impl Person for CSStudent { fn name(&self) -> String { self.name.clone() } } impl CompSciStudent for CSStudent { fn git_username(&self) -> String { self.git_username.clone() } } fn main() { let student = CSStudent { name: String::from("Alice"), university: String::from("MIT"), fav_language: String::from("Rust"), git_username: String::from("alice_codes"), }; let greeting = comp_sci_student_greeting(&student); println!("{}", greeting); }
関連項目:
スーパートレイトに関する The Rust Programming Language の章
重複するトレイトの曖昧さを解消する
型は多くの異なるトレイトを実装できます。2つのトレイトがどちらも 関数に同じ名前を要求する場合はどうなるでしょうか? たとえば、多くのトレイトが get() という名前のメソッドを持つかもしれません。戻り値の型が異なることさえあります!
朗報です。各トレイト実装はそれぞれ独自の impl ブロックを持つため、 どのトレイトの get メソッドを実装しているのかは明確です。
では、それらのメソッドを_呼び出す_ときはどうでしょうか? それらの曖昧さを解消するには、 完全修飾構文を使用する必要があります。
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, }; // この行をコメント解除すると、次のようなエラーが発生します // "multiple `get` found"。結局のところ、`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() { // この呼び出しは `println!("Hello!")` へと展開されます say_hello!() }
では、なぜマクロは有用なのでしょうか?
-
繰り返しを避けるため。似たような機能が複数の場所で必要になるものの、 型が異なるというケースは数多くあります。多くの場合、マクロを書くことは コードの繰り返しを避ける有用な方法です。(これについては後ほど詳しく)
-
ドメイン固有言語。マクロを使うと、特定の目的のために特別な構文を定義できます。 (これについては後ほど詳しく)
-
可変長インターフェイス。可変個の引数を取るインターフェイスを定義したい場合があります。 その例が
println!で、これはフォーマット文字列に応じて任意の数の引数を取ることができます。 (これについては後ほど詳しく)
構文
以降の小節では、Rustでマクロを定義する方法を示します。 基本的な考え方は3つあります:
指定子
マクロの引数にはドル記号 $ がプレフィックスとして付き、 指定子 による型注釈が付けられます:
macro_rules! create_function { // このマクロは指定子 `ident` の引数を取り、 // `$func_name` という名前の関数を作成します。 // `ident` 指定子は変数名/関数名に使用されます。 ($func_name:ident) => { fn $func_name() { // `stringify!` マクロは `ident` を文字列に変換します。 println!("あなたは {:?}() を呼び出しました", stringify!($func_name)); } }; } // 上記のマクロで `foo` と `bar` という名前の関数を作成します。 create_function!(foo); create_function!(bar); macro_rules! print_result { // このマクロは型 `expr` の式を取り、その結果とともに // 文字列として出力します。 // `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(可視性修飾子)
完全な一覧については、Rust Reference を参照してください。
オーバーロード
マクロは、さまざまな引数の組み合わせを受け取れるようにオーバーロードできます。 その点で、macro_rules! は match ブロックと同様に動作します:
// `test!` は `$left` と `$right` を比較します // 呼び出し方に応じて、異なる方法で: 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回以上繰り返せることを示したりできます。
次の例では、マッチャーを $(...),+ で囲むことで、カンマで区切られた1つ以上の式にマッチします。 また、最後のケースではセミコロンが省略可能であることにも注意してください。
// `find_min!` は任意の数の引数の最小値を計算します。 macro_rules! find_min { // 基底ケース: ($x:expr) => ($x); // `$x` の後に少なくとも1つの `$y,` が続く ($x:expr, $($y:expr),+) => ( // 末尾の `$y` に対して `find_min!` を呼び出す std::cmp::min($x, find_min!($($y),+)) ) } fn main() { println!("{}", find_min!(1)); println!("{}", find_min!(1 + 2, 2)); println!("{}", find_min!(5, 2 * 3, 4)); }
DRY(繰り返しを避ける)
マクロを使うと、関数やテストスイートの共通部分を切り出すことで、DRY なコードを書けます。 以下は、Vec<T> に対する +=、*=、-= 演算子を実装してテストする例です。
use std::ops::{Add, Mul, Sub}; macro_rules! assert_equal_len { // `tt`(トークンツリー)指定子は、演算子やトークンに // 使用されます。 ($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 構成要素へ展開されるため、これは完全に有効な 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; // 型を整数に強制する 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 を使うことはできます(ただし、それがテストや簡単なプロトタイプでない限り、そうしないでください)。
エラーハンドリングについてより厳密な議論は、公式の本のエラーハンドリングの章を参照してください。
panic
これから見る最も単純なエラー処理メカニズムは panic です。これはエラーメッセージを出力し、 スタックの巻き戻しを開始し、通常はプログラムを終了します。 ここでは、エラー条件で明示的に panic を呼び出します。
fn drink(beverage: &str) { // 糖分の多い飲み物を飲みすぎるべきではありません。 if beverage == "lemonade" { panic!("AAAaaaaa!!!!"); } println!("Some refreshing {} is all I need.", beverage); } fn main() { drink("water"); drink("lemonade"); drink("still water"); }
drink への最初の呼び出しは動作します。2 番目はパニックするため、3 番目は決して呼び出されません。
abort と unwind
前のセクションでは、エラー処理メカニズムである panic を説明しました。 panic の設定に基づいて、異なるコードパスを条件付きでコンパイルできます。現在利用可能な値は unwind と abort です。
先ほどのレモネードの例を土台にして、panic 戦略を明示的に使用し、異なる行のコードを実行します。
fn drink(beverage: &str) { // 砂糖入りの飲み物を飲み過ぎてはいけません。 if beverage == "lemonade" { if cfg!(panic = "abort") { println!("This is not your party. Run!!!!"); } else { println!("Spit it out!!!!"); } } else { println!("Some refreshing {} is all I need.", beverage); } } fn main() { drink("water"); drink("lemonade"); }
次は、drink() の書き換えに焦点を当て、unwind キーワードを明示的に使用する別の例です。
#[cfg(panic = "unwind")] fn ah() { println!("Spit it out!!!!"); } #[cfg(not(panic = "unwind"))] fn ah() { println!("This is not your party. Run!!!!"); } fn drink(beverage: &str) { if beverage == "lemonade" { ah(); } else { println!("Some refreshing {} is all I need.", beverage); } } fn main() { drink("water"); drink("lemonade"); }
panic 戦略は、コマンドラインから abort または unwind を使用して設定できます。
rustc lemonade.rs -C panic=abort
Option と unwrap
前回の例では、プログラムを意図的に失敗させられることを示しました。 砂糖入りレモネードを飲んだら panic するようにプログラムに指示しました。 では、何らかの 飲み物を期待しているのに受け取らなかった場合はどうでしょうか? その場合も同じくらい悪い状況なので、処理する必要があります!
レモネードの場合と同じように、空文字列("")と照合して調べることも_できます_。 Rust を使っているので、代わりに、飲み物がないケースをコンパイラに指摘してもらいましょう。
std ライブラリにある Option<T> という enum は、不在が 起こり得る場合に使われます。これは 2 つの「オプション」のいずれかとして現れます。
Some(T): 型Tの要素が見つかったNone: 要素が見つからなかった
これらのケースは、match によって明示的に処理することも、unwrap によって暗黙的に処理することもできます。 暗黙的な処理では、内部の要素を返すか、panic します。
expect を使えば panic を手動でカスタマイズできることに注意してください。 ただし、そうしない場合、unwrap は明示的な処理よりも意味の薄い出力になります。 次の例では、明示的な処理によって、必要に応じて panic する選択肢を残しつつ、 より制御された結果を得ています。
// 大人はすべてを見てきており、どんな飲み物でもうまく扱えます。 // すべての飲み物は `match` を使って明示的に処理されます。 fn give_adult(drink: Option<&str>) { // それぞれのケースに対する行動方針を指定します。 match drink { Some("lemonade") => println!("うげっ!甘すぎる。"), Some(inner) => println!("{}?いいですね。", inner), None => println!("飲み物がない?まあいいでしょう。"), } } // 他の人は、砂糖入り飲料を飲む前に `panic` します。 // すべての飲み物は `unwrap` を使って暗黙的に処理されます。 fn drink(drink: Option<&str>) { // `unwrap` は `None` を受け取ると `panic` を返します。 let inside = drink.unwrap(); if inside == "lemonade" { panic!("AAAaaaaa!!!!"); } println!("{}が大好きです!!!!!", inside); } fn main() { let water = Some("water"); let lemonade = Some("lemonade"); let void = None; give_adult(water); give_adult(lemonade); give_adult(void); let coffee = Some("coffee"); let nothing = None; drink(coffee); drink(nothing); }
? で Option を展開する
Option は match 文を使って展開できますが、多くの場合は ? 演算子を使う方が簡単です。x が Option の場合、x? を評価すると、x が Some なら内側の値を返し、そうでなければ現在実行中の関数を終了して None を返します。
fn next_birthday(current_age: Option<u8>) -> Option<String> {
// `current_age` が `None` の場合、これは `None` を返します。
// `current_age` が `Some` の場合、内側の `u8` 値 + 1 が
// `next_age` に代入されます
let next_age: u8 = current_age? + 1;
Some(format!("来年、私は{}歳になります", next_age))
}
複数の ? を連鎖させることで、コードをずっと読みやすくできます。
struct Person { job: Option<Job>, } #[derive(Clone, Copy)] struct Job { phone_number: Option<PhoneNumber>, } #[derive(Clone, Copy)] #[allow(dead_code)] 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 には map() という組み込みメソッドがあります。これは単純な 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!("うーん。{:?} が大好きです", food), None => println!("なんてこと!食べられませんでした。"), } } 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() は、ラップされた値を使って入力された関数を呼び出し、その結果を返します。Option が None の場合は、代わりに None を返します。
次の例では、cookable_v3() は Option<Food> を返します。 and_then() の代わりに map() を使うと Option<Option<Food>> になってしまい、これは eat() に対して無効な型です。
#![allow(dead_code)] #[derive(Debug)] enum Food { CordonBleu, Steak, Sushi } #[derive(Debug)] enum Day { Monday, Tuesday, Wednesday } // Sushi を作るための材料はありません。 fn have_ingredients(food: Food) -> Option<Food> { match food { Food::Sushi => None, _ => Some(food), } } // Cordon Bleu 以外のすべてのレシピがあります。 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) => have_ingredients(food), } } // これは `and_then()` を使うと、より簡潔に書き換えられます: fn cookable_v3(food: Food) -> Option<Food> { have_recipe(food).and_then(have_ingredients) } // そうしない場合、`Option<Option<Food>>` を `flatten()` して // `Option<Food>` を得る必要があります: fn cookable_v2(food: Food) -> Option<Food> { have_recipe(food).map(have_ingredients).flatten() } fn eat(food: Food, day: Day) { match cookable_v3(food) { Some(food) => println!("やった! {:?} には {:?} が食べられます。", day, food), None => println!("なんてこと。{:?} には食べられないのですか?", 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); }
関連項目:
クロージャ、Option、Option::and_then()、および Option::flatten()
Option とデフォルト値を展開する
Option を展開し、それが None の場合にデフォルト値へフォールバックする方法は複数あります。ニーズに合うものを選ぶには、次の点を考慮する必要があります。
- 即時評価と遅延評価のどちらが必要か?
- 元の空の値をそのまま保持する必要があるか、それともインプレースで変更する必要があるか?
or() はチェーン可能で、即時評価され、空の値をそのまま保持する
次の例に示すように、or() はチェーン可能で、引数を即時評価します。or の引数は即時評価されるため、or に渡された変数はムーブされることに注意してください。
#[derive(Debug)] enum Fruit { Apple, Orange, Banana, Kiwi, Lemon } fn main() { let apple = Some(Fruit::Apple); let orange = Some(Fruit::Orange); let no_fruit: Option<Fruit> = None; let first_available_fruit = no_fruit.or(orange).or(apple); println!("最初に利用可能な果物: {:?}", first_available_fruit); // 最初に利用可能な果物: Some(Orange) // `or` は引数をムーブします。 // 上の例では、`or(orange)` が `Some` を返したため、`or(apple)` は呼び出されませんでした。 // しかし、`apple` という名前の変数はそれでもムーブされており、もう使用できません。 // println!("変数 apple はムーブされたため、この行はコンパイルされません: {:?}", apple); // TODO: コンパイラエラーを確認するには、上の行のコメントを外してください }
or_else() はチェーン可能で、遅延評価され、空の値をそのまま保持する
別の選択肢は or_else を使うことです。これもチェーン可能で、次の例に示すように遅延評価されます。
#[derive(Debug)] enum Fruit { Apple, Orange, Banana, Kiwi, Lemon } fn main() { let no_fruit: Option<Fruit> = None; let get_kiwi_as_fallback = || { println!("フォールバックとしてキウイを提供します"); Some(Fruit::Kiwi) }; let get_lemon_as_fallback = || { println!("フォールバックとしてレモンを提供します"); Some(Fruit::Lemon) }; let first_available_fruit = no_fruit .or_else(get_kiwi_as_fallback) .or_else(get_lemon_as_fallback); println!("最初に利用可能な果物: {:?}", first_available_fruit); // フォールバックとしてキウイを提供します // 最初に利用可能な果物: Some(Kiwi) }
get_or_insert() は即時評価され、空の値をインプレースで変更する
Option に値が含まれていることを確実にするには、次の例に示すように、get_or_insert を使ってフォールバック値でインプレースに変更できます。get_or_insert はパラメータを即時評価するため、変数 apple はムーブされることに注意してください。
#[derive(Debug)] enum Fruit { Apple, Orange, Banana, Kiwi, Lemon } fn main() { let mut my_fruit: Option<Fruit> = None; let apple = Fruit::Apple; let first_available_fruit = my_fruit.get_or_insert(apple); println!("first_available_fruit は: {:?}", first_available_fruit); println!("my_fruit は: {:?}", my_fruit); // first_available_fruit は: Apple // my_fruit は: Some(Apple) //println!("`apple` という名前の変数はムーブされました: {:?}", apple); // TODO: コンパイラエラーを確認するには、上の行のコメントを外してください }
get_or_insert_with() は遅延評価され、空の値をインプレースで変更する
フォールバック先の値を明示的に提供する代わりに、次のようにクロージャを get_or_insert_with に渡すことができます。
#[derive(Debug)] enum Fruit { Apple, Orange, Banana, Kiwi, Lemon } fn main() { let mut my_fruit: Option<Fruit> = None; let get_lemon_as_fallback = || { println!("フォールバックとしてレモンを提供します"); Fruit::Lemon }; let first_available_fruit = my_fruit .get_or_insert_with(get_lemon_as_fallback); println!("first_available_fruit は: {:?}", first_available_fruit); println!("my_fruit は: {:?}", my_fruit); // フォールバックとしてレモンを提供します // first_available_fruit は: Lemon // my_fruit は: Some(Lemon) // Option に値がある場合、それは変更されず、クロージャは呼び出されません let mut my_apple = Some(Fruit::Apple); let should_be_apple = my_apple.get_or_insert_with(get_lemon_as_fallback); println!("should_be_apple は: {:?}", should_be_apple); println!("my_apple は変更されていません: {:?}", my_apple); // 出力は次のとおりです。クロージャ `get_lemon_as_fallback` は呼び出されないことに注意してください // should_be_apple は: Apple // my_apple は変更されていません: Some(Apple) }
関連項目:
クロージャ, get_or_insert, get_or_insert_with, ムーブされた変数, or, or_else
Result
Result は、起こり得る_不在_ではなく、起こり得る_エラー_を表す、Option 型のより豊かなバージョンです。
つまり、Result<T, E> は次の 2 つの結果のいずれかを持ちます。
Ok(T): 要素Tが見つかったErr(E): 要素Eとともにエラーが見つかった
慣例として、期待される結果は Ok であり、予期しない結果は Err です。
Option と同様に、Result には多くの関連メソッドがあります。たとえば unwrap() は、要素 T を返すか、panic します。ケース処理については、Result と Option の間に重複する多くのコンビネータがあります。
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 型を特定するには、i32 に対して FromStr トレイトで 実装されている 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 は {}", n), Err(e) => println!("エラー: {}", 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()` などのコンビネーターを使えます。 // それ以外は、この関数は上のものと同じで、次のように読めます: // 両方の値を str からパースできる場合は乗算し、そうでなければエラーを渡します。 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 は {}", n), Err(e) => println!("エラー: {}", e), } } fn main() { // これでも妥当な答えが表示されます。 let twenty = multiply("10", "2"); print(twenty); // 次は、はるかに有用なエラーメッセージを表示します。 let tt = multiply("t", "2"); print(tt); }
Result のエイリアス
特定の Result 型を何度も再利用したい場合はどうすればよいでしょうか? Rust ではエイリアスを作成できることを思い出してください。便利なことに、 対象となる特定の Result 用にエイリアスを定義できます。
モジュールレベルでは、エイリアスの作成が特に役立つことがあります。特定のモジュールで 見つかるエラーは、多くの場合同じ Err 型を持つため、単一のエイリアスで関連する すべての Result を簡潔に定義できます。これは非常に有用なので、std ライブラリでさえ その 1 つである 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")); }
関連項目:
早期リターン
前の例では、コンビネータを使ってエラーを明示的に処理しました。 この場合分けに対処するもう 1 つの方法は、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")); }
ここまでで、コンビネータと早期リターンを使ってエラーを明示的に 処理する方法を学びました。一般的にはパニックを避けたい一方で、 すべてのエラーを明示的に処理するのは面倒です。
次のセクションでは、panic を引き起こす可能性なしに単に unwrap する必要がある場合のために、? を紹介します。
? の導入
panic の可能性なしに、unwrap の単純さだけが欲しい場合があります。 これまで、私たちが本当に望んでいたのは変数を 外へ 取り出すことだったのに、 unwrap はより深くネストすることを強いてきました。これこそが ? の目的です。
Err を見つけたとき、取るべき有効なアクションは 2 つあります。
panic!。これは、可能なら避けようとすでに決めたものですreturn。Errは処理できないことを意味するためです
? は、Err で panic する代わりに return する unwrap と ほぼ1 正確に同等です。コンビネータを使った先ほどの例をどのように簡略化できるか見てみましょう。
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 を使用している場合に、この例をエラーなしでコンパイルして実行するには、 // `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")); }
-
詳細については、re-enter ? を参照してください。 ↩
複数のエラー型
これまでの例は常にとても便利でした。Result は他の Result と相互に作用し、 Option は他の Option と相互に作用します。
ときには、Option が Result と相互に作用する必要があったり、 Result<T, Error1> が Result<T, Error2> と相互に作用する必要があったりします。そのような 場合には、異なるエラー型を、合成可能で扱いやすい方法で管理したいものです。
次のコードでは、unwrap の 2 つのインスタンスが異なるエラー型を生成します。 Vec::first は Option を返す一方、parse::<i32> は Result<i32, ParseIntError> を返します。
fn double_first(vec: Vec<&str>) -> i32 { let first = vec.first().unwrap(); // エラー 1 を生成 2 * first.parse::<i32>().unwrap() // エラー 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: 要素を数値としてパースできない }
以降のセクションでは、この種の問題を処理するためのいくつかの戦略を見ていきます。
Option から Result を取り出す
混在したエラー型を扱う最も基本的な方法は、それらを単に互いの中に埋め込むことです。
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 の場合は処理を続けたいことがあります。transpose 関数は、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.transpose() } 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, "2倍にする最初の項目が無効です") } } 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!("最初の値を2倍にした結果は {}", n), Err(e) => println!("エラー: {}", 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化
元のエラーを保持しつつシンプルなコードを書く方法の 1 つは、それらをBox化することです。欠点は、基になるエラー型が実行時にしか分からず、静的に決定されないことです。
標準ライブラリは、Errorトレイトを実装する任意の型からトレイトオブジェクトBox<Error>への変換を、Fromを介してBoxに実装させることで、エラーのBox化を支援しています。
use std::error; use std::fmt; // `Box<dyn 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()) // Intoトレイトを使ってBoxに変換します。 .and_then(|s| { s.parse::<i32>() .map_err(From::from) // From::from関数ポインタを使って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 していたことに注目してください。
.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 は異なる型同士の 変換ユーティリティなので、これは、エラーが戻り値の型へ変換可能な場所で ? を使うと、 自動的に変換されることを意味します。
ここでは、前の例を ? を使って書き直します。その結果、私たちのエラー型に対して From::from が実装されている場合、map_err は不要になります。
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 の呼び出しを ? に置き換えるのと 非常によく似ています。その結果、トップレベルで分解しなければなりません。
関連項目:
From::from と ?
エラーをラップする
エラーをボックス化する代わりに、独自のエラー型でラップする方法があります。
use std::error; use std::error::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, "少なくとも1つの要素を持つベクターを使用してください"), // ラップされたエラーには追加情報が含まれており、 // source() メソッドを介して利用できます。 DoubleError::Parse(..) => write!(f, "指定された文字列をintとしてパースできませんでした"), } } } 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)?; // ここでは、`DoubleError` を作成するために、`From` の `ParseIntError` 実装 // (上で定義したもの)を暗黙的に使用します。 let parsed = first.parse::<i32>()?; Ok(2 * parsed) } fn print(result: Result<i32>) { match result { Ok(n) => println!("最初の要素を2倍した値は {}", n), Err(e) => { println!("エラー: {}", e); if let Some(source) = e.source() { println!(" 原因: {}", source); } }, } } 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() .filter_map(|s| s.parse::<i32>().ok()) .collect(); println!("Results: {:?}", numbers); }
map_err() と filter_map() で失敗した項目を収集する
map_err はエラーを引数に関数を呼び出すため、これを先ほどの filter_map による解決策に追加することで、反復処理中にそれらを脇に保存できます。
fn main() { let strings = vec!["42", "tofu", "93", "999", "18"]; let mut errors = vec![]; let numbers: Vec<_> = strings .into_iter() .map(|s| s.parse::<u8>()) .filter_map(|r| r.map_err(|e| errors.push(e)).ok()) .collect(); println!("Numbers: {:?}", numbers); println!("Errors: {:?}", errors); }
collect() で操作全体を失敗させる
Result は FromIterator を実装しているため、結果のベクター (Vec<Result<T, E>>) を、ベクターを持つ結果 (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 ライブラリは、primitives を大幅に拡張する多くの独自の型を提供します。例をいくつか示します:
- 可変長の
String:"hello world" - 可変長のベクター:
[1, 2, 3] - オプション型:
Option<i32> - エラー処理型:
Result<i32, i32> - ヒープに割り当てられたポインター:
Box<i32>
関連項目:
Box、スタック、ヒープ
Rust のすべての値は、デフォルトではスタックに割り当てられます。Box<T> を作成することで、値を Box 化 (ヒープに割り当て)できます。Box は、ヒープに割り当てられた型 T の値へのスマートポインタです。Box がスコープ外に出ると、そのデストラクタが呼び出され、内部のオブジェクトが破棄され、ヒープ上のメモリが解放されます。
Box 化された値は * 演算子を使って参照外しできます。これにより、間接参照の層が 1 つ取り除かれます。
use std::mem; #[allow(dead_code)] #[derive(Debug, Clone, Copy)] struct Point { x: f64, y: f64, } // Rectangle は、その左上と右下の角が空間内のどこにあるかで指定できます #[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> { // この点をヒープ上に割り当て、それへのポインタを返します 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 } }; // ヒープに割り当てられた rectangle 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 はスタック上で {} バイトを占有します", mem::size_of_val(&point)); println!("Rectangle はスタック上で {} バイトを占有します", mem::size_of_val(&rectangle)); // Box のサイズ == ポインタのサイズ println!("Box 化された Point はスタック上で {} バイトを占有します", mem::size_of_val(&boxed_point)); println!("Box 化された Rectangle はスタック上で {} バイトを占有します", mem::size_of_val(&boxed_rectangle)); println!("Box 化された Box はスタック上で {} バイトを占有します", mem::size_of_val(&box_in_a_box)); // `boxed_point` に含まれるデータを `unboxed_point` にコピーします let unboxed_point: Point = *boxed_point; println!("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); } // `Vector` は、反復回数を別の変数(`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 はヒープに割り当てられ、伸長可能で、null 終端されません。
&str は、常に有効な UTF-8 シーケンスを指すスライス(&[u8])であり、&[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] になります。
一般に、特殊文字はバックスラッシュ文字 \ でエスケープします。 この方法により、出力できない文字や入力方法が分からない文字であっても、任意の文字を文字列に追加できます。 リテラルのバックスラッシュが必要な場合は、もう 1 つのバックスラッシュでエスケープします: \\
リテラル内に現れる文字列または文字リテラルの区切り文字はエスケープする必要があります: "\"", '\''。
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); // raw 文字列内に引用符が必要な場合は、# のペアを追加する let quotes = r#"And then I said: "There is no escape!""#; println!("{}", quotes); // 文字列内に "# が必要な場合は、区切り文字にさらに多くの # を使用するだけです。 // 最大 255 個の # を使用できます。 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"; // ...しかし Unicode エスケープは使えません // 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 Reference の 'Tokens' chapter に記載されています。
Option
プログラムの一部の失敗を panic! の呼び出しではなく捕捉したい場合があります。これは Option enum を使用することで実現できます。
Option<T> enum には 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` バリアントを unwrap すると、ラップされた値が取り出されます。 println!("{:?} unwraps to {:?}", optional_float, optional_float.unwrap()); // `None` バリアントを unwrap すると `panic!` します println!("{:?} unwraps to {:?}", none, none.unwrap()); }
Result
Option enum は、失敗する可能性がある関数からの戻り値として使用でき、失敗を示すために None を返せることを見てきました。しかし、操作が失敗した_理由_を表現することが重要な場合もあります。そのために Result enum があります。
Result<T, E> enum には2つのバリアントがあります。
Ok(value)は操作が成功したことを示し、その操作によって返されたvalueをラップします。(valueの型はTです)Err(why)は操作が失敗したことを示し、失敗の原因を(できれば)説明するwhyをラップします。(whyの型はEです)
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 { // この操作は `fail` するため、代わりに失敗の理由を // `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)); }
?
match を使って結果を連鎖させると、かなり煩雑になることがあります。幸い、? 演算子を 使うことで再び見通しよくできます。? は Result を返す式の末尾で使われ、 match 式と等価です。その場合、Err(err) 分岐は早期の return 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 => "非正数の対数", MathError::DivisionByZero => "ゼロ除算", MathError::NegativeSquareRoot => "負数の平方根", }), 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 { // ゼロ除算はパニックを引き起こします 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 は余分な領域がある場合に 自身を縮小することもできます。 HashMap::with_capacity(uint) を使用して特定の初期容量を持つ HashMap を作成することも、 HashMap::new() を使用してデフォルトの初期容量を持つ 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?", _ => "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."), } // `HashMap::insert()` は、挿入された値が新しい場合は `None` を返し、 // そうでない場合は `Some(value)` を返す 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を持ち、&strで.get()を呼び出すことができます)
f32 と f64 は Hash を実装して_いない_ことに注意してください。 おそらく、浮動小数点数の精度誤差により、 それらをハッシュマップのキーとして使用すると非常にエラーを起こしやすくなるためです。
すべてのコレクションクラスは、含まれる型もそれぞれ Eq と Hash を実装していれば、 Eq と Hash を実装します。 たとえば、Vec<T> は、T が Hash を実装していれば Hash を実装します。
カスタム型に対して Eq と Hash を実装するのは、たった 1 行で簡単にできます。 #[derive(PartialEq, Eq, Hash)]
残りはコンパイラが行います。詳細をより細かく制御したい場合は、 Eq や Hash、またはその両方を自分で実装できます。 このガイドでは、Hash の実装の詳細は扱いません。
HashMap で struct を使うことを試すために、 非常にシンプルなユーザーログオンシステムを作ってみましょう。
use std::collections::HashMap; // Eq では、その型で PartialEq を derive する必要があります。 #[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); println!("パスワード: {}", password); println!("ログオンを試行しています..."); let logon = Account { username, password, }; match accounts.get(&logon) { Some(account_info) => { println!("ログオンに成功しました!"); println!("名前: {}", account_info.name); println!("メール: {}", account_info.email); }, _ => println!("ログインに失敗しました!"), } } 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 の独自の特徴は、 重複する要素を持たないことが保証されている点です。 これは、どの set コレクションも満たす契約です。 HashSet は実装の 1 つにすぎません。(こちらも参照: BTreeSet)
HashSet にすでに存在する値を挿入すると、 (つまり、新しい値が既存の値と等しく、両方が同じハッシュを持つ場合) 新しい値が古い値を置き換えます。
これは、あるものを 2 つ以上持ちたくない場合や、 すでに持っているかどうかを知りたい場合に非常に便利です。
しかし、set にはそれ以上のことができます。
set には 4 つの主要な操作があります(以下の呼び出しはすべてイテレーターを返します)。
-
union: 両方の set に含まれる一意な要素をすべて取得します。 -
difference: 最初の set には含まれるが、2 番目の set には含まれない要素をすべて取得します。 -
intersection: _両方_の set にのみ含まれる要素をすべて取得します。 -
symmetric_difference: どちらか一方の set には含まれるが、_両方_には含まれない要素をすべて取得します。
次の例で、これらをすべて試してみましょう。
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>>()); }
(例はドキュメントから改変されています。)
Rc
複数の所有権が必要な場合は、Rc(参照カウント)を使用できます。Rc は参照の数、つまり Rc の内部にラップされた値の所有者の数を追跡します。
Rc の参照カウントは、Rc がクローンされるたびに 1 増加し、クローンされた Rc の 1 つがスコープから外れてドロップされるたびに 1 減少します。Rc の参照カウントが 0 になると(つまり、残っている所有者がいないことを意味します)、Rc と値の両方がすべてドロップされます。
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)); // 2つの `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 ^ この行のコメントアウトを解除してみてください }
関連項目:
Arc
スレッド間で共有所有権が必要な場合は、Arc(アトミック参照カウント)を使用できます。この構造体は、Clone 実装を通じて、参照カウンターを増加させながら、メモリヒープ内の値の場所を指す参照ポインターを作成できます。スレッド間で所有権を共有するため、ある値への最後の参照ポインターがスコープ外になると、その変数はドロップされます。
use std::time::Duration; use std::sync::Arc; use std::thread; fn main() { // この変数宣言で、その値が指定されます。 let apple = Arc::new("the same apple"); for _ in 0..10 { // これはメモリヒープ内の参照へのポインターであるため、 // ここでは値の指定はありません。 let apple = Arc::clone(&apple); thread::spawn(move || { // Arc が使用されているため、Arc 変数ポインターの場所に // 割り当てられた値を使用してスレッドを生成できます。 println!("{:?}", apple); }); } // 生成されたスレッドから、すべての Arc インスタンスが出力されるようにします。 thread::sleep(Duration::from_secs(1)); }
Std その他
std ライブラリは、次のようなものをサポートするために、他にも多くの型を提供しています。
- スレッド
- チャネル
- ファイル I/O
これらは、primitives が提供するものをさらに拡張します。
関連項目:
スレッド
Rust は、spawn 関数を介してネイティブ OS スレッドを生成する仕組みを提供しています。この関数の引数はムーブクロージャです。
use std::thread; const NTHREADS: u32 = 10; // これは `main` スレッドです fn main() { // 生成された子スレッドを保持するベクターを作成します。 let mut children = vec![]; for i in 0..NTHREADS { // 別のスレッドを起動します children.push(thread::spawn(move || { println!("これはスレッド番号 {} です", i); })); } for child in children { // スレッドの終了を待機します。結果を返します。 let _ = child.join(); } }
これらのスレッドは OS によってスケジュールされます。
テストケース: map-reduce
Rust を使うと、従来このような試みに伴いがちな多くの悩みなしに、データ処理を非常に簡単に並列化できます。
標準ライブラリは、優れたスレッドプリミティブを標準で提供しています。 これらは、Rust の所有権という概念やエイリアシング規則と組み合わさることで、自動的に データ競合を防ぎます。
エイリアシング規則(1つの書き込み可能な参照 XOR 多数の読み取り可能な参照)は、他のスレッドから見える状態を 操作してしまうことを自動的に防ぎます。(同期が必要な場合には、 Mutex や Channel のような 同期プリミティブがあります。)
この例では、数値のブロック内にあるすべての桁の合計を計算します。 これを、ブロックのチャンクを別々のスレッドに分配することで実現します。各スレッドは 自分の小さな桁ブロックの合計を求め、その後で各 スレッドが生成した中間合計を合計します。
スレッド境界を越えて参照を渡しているにもかかわらず、Rust は、私たちが 読み取り専用の参照だけを渡していること、したがって unsafe な状況やデータ競合が発生し得ないことを理解します。また、 渡している参照には 'static ライフタイムがあるため、Rust は、これらのスレッドがまだ実行中の間に データが破棄されることはないと理解します。(スレッド間で非 static データを共有する必要がある場合は、 Arc のようなスマートポインタを使ってデータを生存させ、非 static ライフタイムを避けることができます。)
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)" は、その直後に // "分解代入" によって、"i" と "data_segment" という // 2つの変数へ "分解" されます 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); // Rust は「式言語」なので、"return" は不要です。 // 各ブロック内で最後に評価された式が、自動的にその値になります。 result })); } /************************************************************************* * "Reduce" フェーズ * * 中間結果を収集し、それらを最終結果へ結合する ************************************************************************/ // 各スレッドの中間結果を、単一の最終的な合計へ結合します。 // // sum() に型ヒントを与えるために "turbofish" ::<> を使用します。 // // TODO: turbofish を使わず、代わりに final_result の型を // 明示的に指定して試してみましょう let final_result = children.into_iter().map(|c| c.join().unwrap()).sum::<u32>(); println!("Final sum result: {}", final_result); }
課題
スレッド数を、ユーザーが入力したデータに依存させるのは賢明ではありません。 ユーザーがたくさんの空白を挿入することにしたらどうなるでしょうか?本当に 2,000個のスレッドを生成したいでしょうか? プログラムの先頭で定義される静的定数によって、データが常に限られた数のチャンクへ分割されるように、 プログラムを変更してください。
関連項目:
- スレッド
- ベクタとイテレータ
- クロージャ、move セマンティクス、および
moveクロージャ - 分解代入
- 型推論を助ける turbofish 記法
- unwrap と expect
- enumerate
チャネル
Rust は、スレッド間の通信のために非同期の channels を提供します。チャネルは、 2つのエンドポイント、すなわち Sender と Receiver の間で、 情報を一方向に流すことを可能にします。
use std::sync::mpsc::{Sender, Receiver}; use std::sync::mpsc; use std::thread; static NTHREADS: i32 = 3; fn main() { // チャネルには2つのエンドポイントがあります: `Sender<T>` と `Receiver<T>`、 // ここで `T` は転送されるメッセージの型です // (型注釈は不要です) let (tx, rx): (Sender<i32>, Receiver<i32>) = mpsc::channel(); let mut children = Vec::new(); for id in 0..NTHREADS { // 送信側のエンドポイントはコピーできます let thread_tx = tx.clone(); // 各スレッドはチャネルを介して自身のidを送信します let child = thread::spawn(move || { // スレッドは `thread_tx` の所有権を取得します // 各スレッドはチャネルにメッセージをキューします thread_tx.send(id).unwrap(); // 送信はノンブロッキング操作であり、スレッドは // メッセージを送信した直後に続行します println!("スレッド {} が終了しました", 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("おっと!子スレッドがパニックしました"); } // メッセージが送信された順序を表示します println!("{:?}", ids); }
ファイルパス
Path 型は、基盤となるファイルシステム内のファイルパスを表します。すべてのプラットフォームにわたって、プラットフォーム固有のパスのセマンティクスと区切り文字を抽象化する単一の std::path::Path があります。必要に応じて use std::path::Path; でスコープに導入します。
Path は OsStr から作成でき、パスが指すファイル/ディレクトリから情報を取得するためのいくつかのメソッドを提供します。
Path は不変です。Path の所有版は PathBuf です。Path と PathBuf の関係は、str と String の関係に似ています。 PathBuf はインプレースで変更でき、Path にデリファレンスできます。
Path は内部的に UTF-8 文字列として表現されている_わけではなく_、代わりに OsString として格納されていることに注意してください。そのため、Path を &str に変換することはコストなしではなく、失敗する可能性があります(Option が返されます)。ただし、Path はそれぞれ into_os_string と as_os_str を使用して、OsString または &OsStr に自由に変換できます。
use std::path::Path; fn main() { // `&'static str` から `Path` を作成する let path = Path::new("."); // `display` メソッドは `Display` 可能な構造体を返す let _display = path.display(); // `join` は OS 固有の区切り文字を使用して、パスをバイトコンテナーと // 結合し、`PathBuf` を返す let mut new_path = path.join("a").join("b"); // `push` は `PathBuf` を `&Path` で拡張する new_path.push("c"); new_path.push("myfile.tar.gz"); // `set_file_name` は `PathBuf` のファイル名を更新する new_path.set_file_name("package.tgz"); // `PathBuf` を文字列スライスに変換する match new_path.to_str() { None => panic!("新しいパスは有効な UTF-8 シーケンスではありません"), Some(s) => println!("新しいパスは {} です", s), } }
他の Path メソッドと Metadata 構造体も必ず確認してください。
関連項目:
ファイル I/O
File 構造体は、開かれたファイルを表し(ファイルディスクリプターをラップします)、 基盤となるファイルへの読み取りおよび/または書き込みアクセスを提供します。
ファイル I/O を行う際には多くの問題が起こり得るため、すべての File メソッドは io::Result<T> 型を返します。これは Result<T, io::Error> のエイリアスです。
これにより、すべての 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
ナイーブなアプローチ
これは、ファイルから行を読み取るための初心者による最初の実装としては、妥当な最初の試みかもしれません。
#![allow(unused)] fn main() { use std::fs::read_to_string; fn read_lines(filename: &str) -> Vec<String> { let mut result = Vec::new(); for line in read_to_string(filename).unwrap().lines() { result.push(line.to_string()) } result } }
lines() メソッドはファイル内の行に対するイテレーターを返すため、インラインで map を実行して結果を collect することもでき、より簡潔で流れるような式になります。
#![allow(unused)] fn main() { use std::fs::read_to_string; fn read_lines(filename: &str) -> Vec<String> { read_to_string(filename) .unwrap() // ファイル読み取りエラーが発生する可能性がある場合はパニックする .lines() // 文字列を文字列スライスのイテレーターに分割する .map(String::from) // 各スライスを文字列にする .collect() // それらをまとめてベクターに集める } }
上記のどちらの例でも、lines() から返される &str 参照を、所有される型である String に変換する必要があることに注意してください。それぞれ .to_string() と String::from を使用しています。
より効率的なアプローチ
ここでは、開いている File の所有権を BufReader 構造体に渡します。BufReader は内部バッファーを使用して中間的なアロケーションを削減します。
また、各行についてメモリ上に新しい String オブジェクトを割り当てるのではなく、イテレーターを返すように read_lines を更新します。
use std::fs::File; use std::io::{self, BufRead}; use std::path::Path; fn main() { // File hosts.txt は現在のパスに存在している必要がある if let Ok(lines) = read_lines("./hosts.txt") { // イテレーターを消費し、(Optional の)String を返す for line in lines.map_while(Result::ok) { println!("{}", line); } } } // 出力は Result でラップされ、エラーに対するマッチングを可能にしている。 // ファイルの行の Reader に対する Iterator を返す。 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.txt
$ rustc read_lines.rs && ./read_lines
127.0.0.1
192.168.0.1
(File::open は引数としてジェネリックな AsRef<Path> を期待するため、where キーワードを使用して、ジェネリックな read_lines() メソッドにも同じジェネリック制約を定義していることに注意してください。)
このプロセスは、ファイルの内容全体を含む 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::process::Child 構造体は子プロセスを表し、パイプを介して基盤となるプロセスとやり取りするための stdin、stdout、stderr ハンドルを公開します。
use std::io::prelude::*;
use std::process::{Command, Stdio};
static PANGRAM: &'static str =
"the quick brown fox jumps over the lazy dog\n";
fn main() {
// `wc` コマンドを起動します
let mut cmd = if cfg!(target_family = "windows") {
let mut cmd = Command::new("powershell");
cmd.arg("-Command").arg("$input | Measure-Object -Line -Word -Character");
cmd
} else {
Command::new("wc")
};
let process = match cmd
.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 が終了するまで待機したい場合は、Child::wait を呼び出す必要があります。これは process::ExitStatus を返します。
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::*;
#[cfg(target_family = "unix")]
use std::os::unix;
#[cfg(target_family = "windows")]
use std::os::windows;
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<()>` を返す
#[cfg(target_family = "unix")] {
unix::fs::symlink("../b.txt", "a/c/b.txt").unwrap_or_else(|why| {
println!("! {:?}", why.kind());
});
}
#[cfg(target_family = "windows")] {
windows::fs::symlink_file("../b.txt", "a/c/b.txt").unwrap_or_else(|why| {
println!("! {:?}", why.to_string());
});
}
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`");
// ディレクトリの内容を読み取り、`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)
}
関連項目:
プログラム引数
標準ライブラリ
コマンドライン引数には std::env::args を使ってアクセスできます。これは 各引数に対して String を生成するイテレータを返します。
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"].
クレート
あるいは、コマンドラインアプリケーションを作成する際に追加機能を提供できるクレートが数多くあります。より人気のあるコマンドライン引数クレートの 1 つが clap です。
引数の解析
マッチングは、単純な引数を解析するために使用できます。
use std::env;
fn increase(number: i32) {
println!("{}", number + 1);
}
fn decrease(number: i32) {
println!("{}", number - 1);
}
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!");
},
// 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();
},
}
},
// その他すべての場合
_ => {
// ヘルプメッセージを表示する
help();
}
}
}
プログラムに match_args.rs という名前を付け、次のように rustc match_args.rs でコンパイルした場合、以下のように実行できます。
$ ./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)を提供します。外部関数は、外部ライブラリの名前を含む #[link] 属性で注釈された extern ブロック内で宣言する必要があります。
use std::fmt;
// この extern ブロックは libm ライブラリにリンクします
#[cfg(target_family = "windows")]
#[link(name = "msvcrt")]
extern {
// これは外部関数です
// 単精度複素数の平方根を計算します
fn csqrtf(z: Complex) -> Complex;
fn ccosf(z: Complex) -> Complex;
}
#[cfg(target_family = "unix")]
#[link(name = "m")]
extern {
// これは外部関数です
// 単精度複素数の平方根を計算します
fn csqrtf(z: Complex) -> Complex;
fn ccosf(z: Complex) -> Complex;
}
// 外部関数の呼び出しは unsafe と見なされるため、
// それらの周りに安全なラッパーを書くのが一般的です。
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 つのスタイルがあります。
また、Rust はテスト用の追加の依存関係を指定するためのサポートも備えています。
関連項目
- テストに関する The Book の章
- ドキュメントテストに関する API Guidelines
単体テスト
テストは、テスト対象ではないコードが期待どおりに機能していることを検証する Rust 関数です。テスト関数の本体は通常、何らかのセットアップを行い、 テストしたいコードを実行し、その結果が期待どおりであるかをアサートします。
ほとんどの単体テストは、#[cfg(test)] attribute を付けた tests mod に配置します。 テスト関数には #[test] 属性を付けます。
テスト関数内の何かが panics すると、テストは失敗します。いくつかの 補助 macros があります。
assert!(expression)- expression がfalseに評価されるとパニックします。assert_eq!(left, right)とassert_ne!(left, right)- 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 {
// この便利なイディオムに注目してください: 外側の (mod tests に対する) スコープから名前をインポートしています。
use super::*;
#[test]
fn test_add() {
assert_eq!(add(1, 2), 3);
}
#[test]
fn test_bad_add() {
// このアサートは発火し、テストは失敗します。
// private 関数もテストできることに注意してください!
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 = を受け取ります。関数が複数の方法でパニックする可能性がある場合、 テストが正しいパニックをテストしていることを確認するのに役立ちます。
注: Rust では省略形 #[should_panic = "message"] も使用できます。これは #[should_panic(expected = "message")] とまったく同じように動作します。どちらも有効ですが、後者の方がより一般的に 使用され、より明示的だと考えられています。
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);
}
#[test]
#[should_panic = "Divide result is zero"] // これも動作します
fn test_specific_panic_shorthand() {
divide_non_zero_result(1, 10);
}
}
これらのテストを実行すると、次のようになります。
$ cargo test
running 4 tests
test tests::test_any_panic ... ok
test tests::test_divide ... ok
test tests::test_specific_panic ... ok
test tests::test_specific_panic_shorthand ... ok
test result: ok. 4 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; 3 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 3 tests
test tests::test_any_panic ... ok
test tests::test_specific_panic ... ok
test tests::test_specific_panic_shorthand ... ok
test result: ok. 3 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 でそれらを実行します。
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 プロジェクトをドキュメント化する主な方法は、ソースコードに 注釈を付けることです。ドキュメンテーションコメントは CommonMark Markdown specification で記述され、その中でコードブロックをサポートします。 Rust は正確性を保証するため、これらのコードブロックはコンパイルされ、 ドキュメントテストとして使用されます。
/// 最初の行は、関数を説明する短い要約です。
///
/// 次の行では詳細なドキュメントを提示します。コードブロックは
/// 3 つのバッククォートで始まり、内部に暗黙の `fn main()` と
/// `extern crate <cratename>` を持ちます。`playground` ライブラリ
/// クレートをテストしているか、Playground の Test アクションを使用していると仮定します。
///
/// ```
/// let result = playground::add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
/// 通常、doc コメントには "Examples"、"Panics"、"Failures" セクションを含めることができます。
///
/// 次の関数は 2 つの数値を除算します。
///
/// # Examples
///
/// ```
/// let result = playground::div(10, 2);
/// assert_eq!(result, 5);
/// ```
///
/// # Panics
///
/// 2 番目の引数がゼロの場合、この関数はパニックします。
///
/// ```rust,should_panic
/// // ゼロ除算でパニックする
/// playground::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 playground
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
ドキュメントテストの背後にある動機
ドキュメントテストの主な目的は、機能を実行する例として機能することであり、 これは最も重要なガイドラインの 1 つです。 これにより、ドキュメント内の例を完全なコードスニペットとして使用できます。 しかし、? を使用すると、main が unit を返すためコンパイルに失敗します。 ドキュメントから一部のソース行を隠す機能が役に立ちます。 fn try_main() -> Result<(), ErrorType> を書いてそれを隠し、 隠された main でそれを unwrap できます。複雑に聞こえますか? 次に例を示します。
/// doc テストで隠された `try_main` を使用する。
///
/// ```
/// # // 隠し行は `#` 記号で始まりますが、それでもコンパイル可能です!
/// # fn try_main() -> Result<(), String> { // ドキュメントに表示される本体をラップする行
/// let res = playground::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
統合テスト
単体テストは、1 つのモジュールを一度に単独でテストします。単体テストは小さく、 プライベートなコードもテストできます。統合テストはクレートの外部にあり、他のコードと同じように 公開インターフェイスだけを使用します。その目的は、ライブラリの多くの部分が連携して正しく動作することを テストすることです。
Cargo は、src の隣にある tests ディレクトリ内で統合テストを探します。
ファイル src/lib.rs:
// これを `adder` という名前のクレートで定義する。
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
テストを含むファイル: tests/integration_test.rs:
#[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 ディレクトリ内の各 Rust ソースファイルは、個別のクレートとしてコンパイルされます。 統合テスト間でコードを共有するために、公開関数を持つモジュールを作成し、 それをテスト内でインポートして使用できます。
ファイル tests/common/mod.rs:
pub fn setup() {
// 必要なファイル/ディレクトリの作成や、サーバーの起動などの
// セットアップコード。
}
テストを含むファイル: tests/integration_test.rs
// common モジュールをインポートする。
mod common;
#[test]
fn test_add() {
// common のコードを使用する。
common::setup();
assert_eq!(adder::add(3, 2), 5);
}
モジュールを tests/common.rs として作成しても動作しますが、おすすめしません。 なぜなら、テストランナーがそのファイルをテストクレートとして扱い、その中のテストを実行しようとするためです。
開発依存関係
テスト(またはサンプル、あるいはベンチマーク)のためだけに 依存関係が必要になることがあります。このような依存関係は、 Cargo.toml の [dev-dependencies] セクションに追加します。 これらの依存関係は、このパッケージに依存する他のパッケージには伝播されません。
その一例が pretty_assertions です。これは標準の assert_eq! マクロと assert_ne! マクロを拡張し、色付きの差分を提供します。 ファイル Cargo.toml:
# 標準的なクレートデータは記載していない
[dev-dependencies]
pretty_assertions = "1"
ファイル src/lib.rs:
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq; // テスト専用のクレート。テスト以外のコードでは使用できない。
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
}
関連項目
依存関係の指定に関する Cargo のドキュメント。
unsafe 操作
このセクションの導入として、公式ドキュメントから引用すると、 「コードベース内の unsafe コードの量を最小限に抑えるよう努めるべきです」。これを 念頭に置いて始めましょう!Rust の unsafe アノテーションは、コンパイラによって 設けられている保護を迂回するために使われます。具体的には、unsafe は主に 次の 4 つのことに使われます。
- 生ポインターの参照外し
unsafeな関数またはメソッドの呼び出し(FFI 経由での関数呼び出しを含みます。 本書の前の章を参照してください)- 静的な可変変数へのアクセスまたは変更
- unsafe トレイトの実装
生ポインター
生ポインター * と参照 &T は同じように機能しますが、参照は 借用チェッカーによって有効なデータを指していることが保証されるため、 常に安全です。生ポインターの参照外しは、unsafe ブロック内でのみ 行うことができます。
fn main() { let raw_p: *const u32 = &10; unsafe { assert!(*raw_p == 10); } }
unsafe 関数の呼び出し
一部の関数は 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 については、維持されなければならない前提の 1 つとして、 渡されたポインターが有効なメモリを指しており、その指しているメモリが正しい型で あることが挙げられます。これらの不変条件が維持されていない場合、プログラムの 動作は未定義となり、何が起こるかは分かりません。
インラインアセンブリ
Rust は asm! マクロによってインラインアセンブリをサポートしています。 これは、コンパイラが生成するアセンブリ出力に手書きのアセンブリを埋め込むために使用できます。 通常これは必要ないはずですが、必要な性能やタイミングを他の方法では達成できない場合には必要になることがあります。たとえばカーネルコードで低レベルのハードウェアプリミティブにアクセスする場合にも、この機能が要求されることがあります。
注: ここでの例は x86/x86-64 アセンブリで示していますが、他のアーキテクチャもサポートされています。
インラインアセンブリは現在、以下のアーキテクチャでサポートされています。
- x86 および x86-64
- ARM
- AArch64
- RISC-V
基本的な使い方
可能な限り最も単純な例から始めましょう。
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; unsafe { asm!("nop"); } } }
これは、コンパイラが生成するアセンブリに NOP(no operation)命令を挿入します。 すべての asm! 呼び出しは unsafe ブロック内に置く必要があることに注意してください。これは、任意の命令を挿入してさまざまな不変条件を破る可能性があるためです。挿入される命令は、asm! マクロの第 1 引数に文字列リテラルとして列挙されます。
入力と出力
何もしない命令を挿入するだけではかなり退屈です。実際にデータに作用することをしてみましょう。
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; let x: u64; unsafe { asm!("mov {}, 5", out(reg) x); } assert_eq!(x, 5); } }
これは値 5 を u64 変数 x に書き込みます。 命令を指定するために使用している文字列リテラルが、実際にはテンプレート文字列であることがわかります。 これは Rust のフォーマット文字列と同じ規則に従います。 ただし、テンプレートに挿入される引数は、見慣れているものとは少し異なります。まず、変数がインラインアセンブリの入力なのか出力なのかを指定する必要があります。この場合は出力です。これは out と書くことで宣言しました。 また、アセンブリがその変数をどの種類のレジスタで想定しているかも指定する必要があります。 この場合は reg を指定することで、任意の汎用レジスタに入れています。 コンパイラはテンプレートに挿入する適切なレジスタを選択し、インラインアセンブリの実行が終了した後にそこから変数を読み取ります。
入力も使用する別の例を見てみましょう。
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; let i: u64 = 3; let o: u64; unsafe { asm!( "mov {0}, {1}", "add {0}, 5", out(reg) o, in(reg) i, ); } assert_eq!(o, 8); } }
これは変数 i の入力に 5 を加算し、その結果を変数 o に書き込みます。 このアセンブリがこれを行う具体的な方法は、まず値を i から出力へコピーし、その後それに 5 を加算するというものです。
この例はいくつかのことを示しています。
第一に、asm! では複数のテンプレート文字列引数を使用できることがわかります。それぞれは、互いの間に改行を挟んで結合されたかのように、別々のアセンブリコード行として扱われます。これにより、アセンブリコードを整形しやすくなります。
第二に、入力は out ではなく in と書くことで宣言されることがわかります。
第三に、任意のフォーマット文字列と同様に、引数番号や名前を指定できることがわかります。 インラインアセンブリテンプレートでは、引数が複数回使用されることが多いため、これは特に有用です。 より複雑なインラインアセンブリでは、一般にこの機能を使用することが推奨されます。可読性が向上し、引数の順序を変更せずに命令を並べ替えられるためです。
上記の例をさらに洗練して、mov 命令を避けることができます。
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; let mut x: u64 = 3; unsafe { asm!("add {0}, 5", inout(reg) x); } assert_eq!(x, 8); } }
inout は、入力でもあり出力でもある引数を指定するために使用されることがわかります。 これは、入力と出力を別々に指定する場合とは異なり、両方が同じレジスタに割り当てられることが保証されます。
inout オペランドの入力部分と出力部分に異なる変数を指定することも可能です。
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; let x: u64 = 3; let y: u64; unsafe { asm!("add {0}, 5", inout(reg) x => y); } assert_eq!(y, 8); } }
遅延出力オペランド
Rust コンパイラは、オペランドの割り当てに関して保守的です。out はいつでも書き込まれる可能性があると仮定されるため、他のどの引数とも場所を共有できません。 しかし、最適な性能を保証するには、使用するレジスタをできるだけ少なくすることが重要です。そうすれば、インラインアセンブリブロックの前後でそれらを保存して再読み込みする必要がなくなります。 これを実現するために、Rust は lateout 指定子を提供しています。これは、すべての入力が消費された後にのみ書き込まれる任意の出力に使用できます。この指定子には inlateout バリアントもあります。
以下は、release モードまたはその他の最適化された場合に inlateout を使用_できない_例です。
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; let mut a: u64 = 4; let b: u64 = 4; let c: u64 = 4; unsafe { asm!( "add {0}, {1}", "add {0}, {2}", inout(reg) a, in(reg) b, in(reg) c, ); } assert_eq!(a, 12); } }
最適化されていない場合(例: Debug モード)では、上記の例で inout(reg) a を inlateout(reg) a に置き換えても、期待される結果が得られ続けることがあります。しかし、release モードまたはその他の最適化された場合には、inlateout(reg) a を使用すると、代わりに最終値が a = 16 となり、アサーションが失敗する可能性があります。
これは、最適化された場合には、コンパイラが入力 b と c に同じレジスタを自由に割り当てられるためです。コンパイラは、それらが同じ値を持つことを知っているからです。さらに、inlateout が使用されると、a と c が同じレジスタに割り当てられる可能性があり、その場合、最初の add 命令が変数 c からの初期ロードを上書きしてしまいます。これは、inout(reg) a を使用すると a 用に別個のレジスタが割り当てられることが保証されるのとは対照的です。
しかし、以下の例では、すべての入力レジスタが読み取られた後にのみ出力が変更されるため、inlateout を使用できます。
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; let mut a: u64 = 4; let b: u64 = 4; unsafe { asm!("add {0}, {1}", inlateout(reg) a, in(reg) b); } assert_eq!(a, 8); } }
ご覧のとおり、このアセンブリ断片は a と b が同じレジスタに割り当てられていても正しく動作します。
明示的なレジスタオペランド
一部の命令では、オペランドが特定のレジスタにあることが必要です。 そのため、Rust のインラインアセンブリは、より具体的な制約指定子をいくつか提供しています。 reg は一般にどのアーキテクチャでも利用できますが、明示的なレジスタはアーキテクチャに強く依存します。たとえば x86 では、汎用レジスタ eax、ebx、ecx、edx、ebp、esi、edi などを名前で指定できます。
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; let cmd = 0xd1; unsafe { asm!("out 0x64, eax", in("eax") cmd); } } }
この例では、out 命令を呼び出して、cmd 変数の内容をポート 0x64 に出力します。out 命令はオペランドとして eax(およびそのサブレジスター)しか受け付けないため、eax 制約指定子を使用する必要がありました。
注記: 他のオペランド型とは異なり、明示的なレジスターオペランドはテンプレート文字列内で使用できません。
{}は使用できず、代わりにレジスター名を直接書く必要があります。また、これらはオペランドリストの末尾、他のすべてのオペランド型の後に置かなければなりません。
x86 の mul 命令を使用する次の例を考えてみましょう。
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; fn mul(a: u64, b: u64) -> u128 { let lo: u64; let hi: u64; unsafe { asm!( // x86 の mul 命令は rax を暗黙的な入力として受け取り、 // 乗算の 128 ビット結果を rax:rdx に書き込みます。 "mul {}", in(reg) a, inlateout("rax") b => lo, lateout("rdx") hi ); } ((hi as u128) << 64) + lo as u128 } } }
これは mul 命令を使用して、2 つの 64 ビット入力を乗算し、128 ビットの結果を得ます。 唯一の明示的なオペランドはレジスターであり、これは変数 a から埋めます。 2 番目のオペランドは暗黙的であり、rax レジスターでなければならず、これは変数 b から埋めます。 結果の下位 64 ビットは rax に格納され、そこから変数 lo を埋めます。 上位 64 ビットは rdx に格納され、そこから変数 hi を埋めます。
クロバーされるレジスター
多くの場合、インラインアセンブリは出力としては不要な状態を変更します。 通常、これはアセンブリ内でスクラッチレジスターを使用する必要があるため、または命令がそれ以上調べる必要のない状態を変更するためです。 この状態は一般に「クロバーされる」と呼ばれます。 コンパイラーがインラインアセンブリブロックの前後でこの状態を保存および復元する必要がある場合があるため、このことをコンパイラーに伝える必要があります。
use std::arch::asm; #[cfg(target_arch = "x86_64")] fn main() { // それぞれ 4 バイトのエントリーが 3 つ let mut name_buf = [0_u8; 12]; // 文字列は ebx、edx、ecx の順に ascii として格納されます // ebx は予約されているため、asm はその値を保持する必要があります。 // そのため、メインの asm の前後でそれを push および pop します。 // 64 ビットプロセッサー上の 64 ビットモードでは、 // 32 ビットレジスター(ebx など)の push/pop は許可されないため、代わりに拡張 rbx レジスターを使用する必要があります。 unsafe { asm!( "push rbx", "cpuid", "mov [rdi], ebx", "mov [rdi + 4], edx", "mov [rdi + 8], ecx", "pop rbx", // 値を格納するために配列へのポインターを使用し、 // Rust コードを簡潔にします。その代償として asm 命令がいくつか増えます // ただし、これは `out("ecx") val` のような明示的なレジスター出力とは対照的に、 // asm がどのように動作するかをより明示的に示します // *ポインター自体* は、その背後に書き込まれる場合でも入力にすぎません in("rdi") name_buf.as_mut_ptr(), // cpuid 0 を選択し、eax もクロバーされるものとして指定します inout("eax") 0 => _, // cpuid はこれらのレジスターもクロバーします out("ecx") _, out("edx") _, ); } let name = core::str::from_utf8(&name_buf).unwrap(); println!("CPU Manufacturer ID: {}", name); } #[cfg(not(target_arch = "x86_64"))] fn main() {}
上の例では、cpuid 命令を使用して CPU メーカー ID を読み取っています。 この命令は、サポートされている最大の cpuid 引数を eax に書き込み、CPU メーカー ID を ASCII バイトとして ebx、edx、ecx にその順序で書き込みます。
eax は読み取られることがありませんが、それでもコンパイラーが asm の前にこれらのレジスターに入っていた値を保存できるように、そのレジスターが変更されたことをコンパイラーに伝える必要があります。これは、変数名の代わりに _ を指定して出力として宣言することで行います。これは、出力値が破棄されることを示します。
このコードは、ebx が LLVM による予約済みレジスターであるという制限にも対処しています。つまり、LLVM はそのレジスターを完全に制御していると想定しており、asm ブロックを抜ける前に元の状態へ復元されなければならないため、コンパイラーが一般レジスタークラス(例: in(reg))を満たすために使用する場合を除いて、入力または出力として使用できません。これにより、予約済みレジスターを使用する場合の reg オペランドは危険になります。同じレジスターを共有しているため、入力や出力を知らないうちに破損させる可能性があるからです。
これに対処するため、rdi を使用して出力配列へのポインターを格納し、push によって ebx を保存し、asm ブロック内で ebx から配列へ読み取り、その後 pop によって ebx を元の状態へ復元します。push と pop は、レジスター全体が保存されることを保証するために、完全な 64 ビット版である rbx レジスターを使用します。32 ビットターゲットでは、コードは代わりに push/pop で ebx を使用します。
これは、asm コード内で使用するスクラッチレジスターを取得するために、一般レジスタークラスとともに使用することもできます。
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; // シフトと加算を使用して x に 6 を掛けます let mut x: u64 = 4; unsafe { asm!( "mov {tmp}, {x}", "shl {tmp}, 1", "shl {x}, 2", "add {x}, {tmp}", x = inout(reg) x, tmp = out(reg) _, ); } assert_eq!(x, 4 * 6); } }
シンボルオペランドと ABI クロバー
デフォルトでは、asm! は、出力として指定されていないレジスターの内容はアセンブリコードによって保持されると想定します。asm! の clobber_abi 引数は、指定された呼び出し規約 ABI に従って必要なクロバーオペランドを自動的に挿入するようコンパイラーに伝えます。その ABI で完全に保持されないレジスターは、すべてクロバーされるものとして扱われます。複数の clobber_abi 引数を指定でき、指定されたすべての ABI からのすべてのクロバーが挿入されます。
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; extern "C" fn foo(arg: i32) -> i32 { println!("arg = {}", arg); arg * 2 } fn call_foo(arg: i32) -> i32 { unsafe { let result; asm!( "call {}", // 呼び出す関数ポインター in(reg) foo, // rdi の第 1 引数 in("rdi") arg, // rax の戻り値 out("rax") result, // "C" 呼び出し規約によって保持されないすべてのレジスターを // クロバーされるものとしてマークします。 clobber_abi("C"), ); result } } } }
レジスターテンプレート修飾子
場合によっては、テンプレート文字列に挿入されるときにレジスター名がどのようにフォーマットされるかを細かく制御する必要があります。これは、あるアーキテクチャのアセンブリ言語に同じレジスターに対する複数の名前があり、それぞれが通常はそのレジスターのサブセットに対する「ビュー」である場合に必要です(例: 64 ビットレジスターの下位 32 ビット)。
デフォルトでは、コンパイラーは常にレジスター全体のサイズを指す名前を選択します(例: x86-64 では rax、x86 では eax など)。
このデフォルトは、フォーマット文字列の場合と同様に、テンプレート文字列オペランドに修飾子を使用することで上書きできます。
```rust
# #[cfg(target_arch = "x86_64")] {
use std::arch::asm;
let mut x: u16 = 0xab;
unsafe {
asm!("mov {0:h}, {0:l}", inout(reg_abcd) x);
}
assert_eq!(x, 0xabab);
# }
この例では、reg_abcd レジスタクラスを使用して、レジスタ割り当て器が、最初の 2 バイトを独立してアドレス指定できる 4 つのレガシー x86 レジスタ(ax、bx、cx、dx)に制限されるようにしています。
レジスタ割り当て器が x を ax レジスタに割り当てることを選択したと仮定しましょう。 h 修飾子はそのレジスタの上位バイトのレジスタ名を出力し、l 修飾子は下位バイトのレジスタ名を出力します。したがって、asm コードは mov ah, al として展開され、値の下位バイトを上位バイトにコピーします。
オペランドでより小さいデータ型(例: u16)を使用し、テンプレート修飾子の使用を忘れた場合、コンパイラは警告を出力し、使用すべき正しい修飾子を提案します。
メモリアドレスオペランド
アセンブリ命令では、メモリアドレス/メモリ位置を介して渡されるオペランドが必要になることがあります。 ターゲットアーキテクチャで指定されたメモリアドレス構文を手動で使用する必要があります。 たとえば、x86/x86_64 で Intel アセンブリ構文を使用する場合、入力/出力を [] で囲んで、それらがメモリオペランドであることを示すべきです。
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; fn load_fpu_control_word(control: u16) { unsafe { asm!("fldcw [{}]", in(reg) &control, options(nostack)); } } } }
ラベル
名前付きラベルの再利用は、それがローカルであるかどうかにかかわらず、アセンブラまたはリンカエラーを引き起こしたり、その他の奇妙な動作を引き起こしたりする可能性があります。名前付きラベルの再利用は、次のようなさまざまな方法で発生する可能性があります。
- 明示的に: 1 つの
asm!ブロック内でラベルを複数回使用する、または複数のブロックにわたって複数回使用する。 - インライン化を介して暗黙的に: コンパイラは、たとえばそれを含む関数が複数の場所でインライン化される場合に、
asm!ブロックの複数のコピーをインスタンス化することが許可されています。 - LTO を介して暗黙的に: LTO によって、他のクレート のコードが同じコード生成ユニットに配置されることがあり、その結果、任意のラベルが持ち込まれる可能性があります。
そのため、インラインアセンブリコード内では GNU アセンブラの数値 ローカルラベル のみを使用すべきです。アセンブリコードでシンボルを定義すると、シンボル定義の重複により、アセンブラおよび/またはリンカエラーにつながる可能性があります。
さらに、x86 でデフォルトの Intel 構文を使用している場合、LLVM のバグ により、0 や 1 の数字だけで構成されるラベル(例: 0、11、101010)は、バイナリ値として解釈されてしまう可能性があるため、使用すべきではありません。options(att_syntax) を使用すると曖昧さは避けられますが、それは asm! ブロック_全体_の構文に影響します。(options の詳細については、下記の オプション を参照してください。)
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; let mut a = 0; unsafe { asm!( "mov {0}, 10", "2:", "sub {0}, 1", "cmp {0}, 3", "jle 2f", "jmp 2b", "2:", "add {0}, 2", out(reg) a ); } assert_eq!(a, 5); } }
これは {0} レジスタ値を 10 から 3 までデクリメントし、その後 2 を加算して a に格納します。
この例はいくつかのことを示しています。
- まず、同じ数値を同じインラインブロック内で複数回ラベルとして使用できること。
- 次に、数値ラベルが参照として使用される場合(たとえば命令オペランドとして)、接尾辞 “b”(“backward”)または ”f”(“forward”)を数値ラベルに追加すべきであること。そうすると、その方向にある、この数値で定義された最も近いラベルを参照します。
オプション
デフォルトでは、インラインアセンブリブロックは、カスタム呼び出し規約を持つ外部 FFI 関数呼び出しと同じように扱われます。つまり、メモリを読み書きする可能性があり、観測可能な副作用を持つ可能性がある、というように扱われます。しかし、多くの場合、アセンブリコードが実際に何をしているのかについて、より多くの情報をコンパイラに与え、よりよく最適化できるようにすることが望ましいです。
前の例の add 命令を取り上げましょう。
#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; let mut a: u64 = 4; let b: u64 = 4; unsafe { asm!( "add {0}, {1}", inlateout(reg) a, in(reg) b, options(pure, nomem, nostack), ); } assert_eq!(a, 8); } }
オプションは、asm! マクロの省略可能な最後の引数として指定できます。ここでは 3 つのオプションを指定しています。
pureは、asm コードに観測可能な副作用がなく、その出力が入力のみに依存することを意味します。これにより、コンパイラのオプティマイザはインライン asm の呼び出し回数を減らしたり、完全に削除したりすることさえ可能になります。nomemは、asm コードがメモリを読み書きしないことを意味します。デフォルトでは、コンパイラはインラインアセンブリが、それにアクセス可能な任意のメモリアドレス(例: オペランドとして渡されたポインタ経由、またはグローバル)を読み書きできると仮定します。nostackは、asm コードがスタックにデータをプッシュしないことを意味します。これにより、コンパイラは x86-64 のスタックレッドゾーンなどの最適化を使用して、スタックポインタの調整を避けることができます。
これらにより、コンパイラは asm! を使用するコードをよりよく最適化できます。たとえば、出力が不要な純粋な asm! ブロックを削除できます。
利用可能なオプションの完全な一覧とその効果については、リファレンスを参照してください。
互換性
Rust 言語は急速に進化しており、そのため、可能な限り前方互換性を確保する努力にもかかわらず、特定の互換性の問題が発生する可能性があります。
生識別子
Rust には、多くのプログラミング言語と同様に「キーワード」という概念があります。 これらの識別子は言語にとって意味を持つため、変数名、関数名、その他の場所では使用できません。 生識別子を使用すると、通常は許可されない場所でキーワードを使用できます。 これは、Rust が新しいキーワードを導入し、古いエディションの Rust を使用しているライブラリに、新しいエディションで導入されたキーワードと同じ名前の変数や関数がある場合に特に便利です。
たとえば、2015 エディションの Rust でコンパイルされたクレート foo が、try という名前の関数をエクスポートしている場合を考えてみましょう。このキーワードは 2018 エディションの新機能のために予約されているため、生識別子がなければ、その関数を指す方法がありません。
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
これは生識別子を使って次のように書けます。
extern crate foo;
fn main() {
foo::r#try();
}
メタ
いくつかのトピックは、プログラムの実行方法に直接関係するものではありませんが、 ツールやインフラストラクチャのサポートを提供し、すべての人にとって物事をより良くします。 これらのトピックには次のものがあります。
- ドキュメント: 同梱されている
rustdocを使用して、ユーザー向けのライブラリドキュメントを生成します。 - Playground: ドキュメントに Rust Playground を統合します。
ドキュメント
cargo doc を使用すると、target/doc にドキュメントをビルドできます。cargo doc --open は、それを Web ブラウザーで自動的に開きます。
cargo test を使用すると、すべてのテスト(ドキュメントテストを含む)を実行できます。また、cargo test --doc を使用すると、ドキュメントテストのみを実行できます。
これらのコマンドは、必要に応じて適切に rustdoc(および rustc)を呼び出します。
ドキュメントコメント
ドキュメントコメントは、ドキュメントが必要な大規模プロジェクトで非常に有用です。 rustdoc を実行すると、これらのコメントがコンパイルされてドキュメントになります。 これらは /// で示され、Markdown をサポートしています。
#![crate_name = "doc"]
/// ここでは人間を表します
pub struct Person {
/// Juliet がどれほどそれを嫌っていても、人には名前が必要です
name: String,
}
impl Person {
/// 指定された名前を持つ person を作成します。
///
/// # 例
///
/// ```
/// // コメント内のフェンスの間に Rust コードを記述できます
/// // `rustdoc` に --test を渡すと、それをテストすることさえできます!
/// use doc::Person;
/// let person = Person::new("name");
/// ```
pub fn new(name: &str) -> Person {
Person {
name: name.to_string(),
}
}
/// 親しみやすい挨拶をします!
///
/// 呼び出し対象の `Person` に対して "Hello, [name](Person::name)" と言います。
pub fn hello(&self) {
println!("Hello, {}!", self.name);
}
}
fn main() {
let john = Person::new("John");
john.hello();
}
テストを実行するには、まずコードをライブラリとしてビルドし、その後 rustdoc にライブラリの場所を伝えて、 各 doctest プログラムにリンクできるようにします。
$ rustc doc.rs --crate-type lib
$ rustdoc --test --extern doc="libdoc.rlib" doc.rs
ドキュメント属性
以下は、rustdoc で使用される最も一般的な #[doc] 属性のいくつかの例です。
inline
別ページにリンクする代わりに、ドキュメントをインライン化するために使用されます。
#[doc(inline)]
pub use bar::Bar;
/// bar のドキュメント
pub mod bar {
/// Bar のドキュメント
pub struct Bar;
}
no_inline
別ページや他の場所へのリンクを防ぐために使用されます。
// libcore/prelude からの例
#[doc(no_inline)]
pub use crate::mem::drop;
hidden
これを使用すると、rustdoc にこれをドキュメントに含めないよう指示します。
// futures-rs ライブラリからの例
#[doc(hidden)]
pub use self::async_await::*;
ドキュメントについては、rustdoc がコミュニティで広く使用されています。これは std ライブラリのドキュメントを生成するために使用されているものです。
関連項目:
- The Rust Book: 役立つドキュメントコメントを書く
- The rustdoc Book
- The Reference: ドキュメントコメント
- RFC 1574: API ドキュメント規約
- RFC 1946: ドキュメントコメントから他の項目への相対リンク(intra-rustdoc links)
- コメント向けのドキュメントスタイルガイドはありますか?(reddit)
プレイグラウンド
Rust Playground は、Web インターフェイスを通じて Rust コードを試すための手段です。
mdbook で使用する
mdbook では、コード例を実行可能かつ編集可能にできます。
fn main() { println!("Hello World!"); }
これにより、読者はコードサンプルを実行できるだけでなく、修正や調整も行えます。ここで重要なのは、コードフェンスブロックにカンマで区切って editable という単語を追加することです。
```rust,editable
//...ここにコードを配置してください
```
さらに、mdbook がビルドやテスト時にコードをスキップするようにしたい場合は、ignore を追加できます。
```rust,editable,ignore
//...ここにコードを配置してください
```
docs で使用する
公式 Rust ドキュメント の一部で、"Run" と表示されたボタンを見たことがあるかもしれません。このボタンは、コードサンプルを Rust Playground の新しいタブで開きます。この機能は、html_playground_url という #[doc] 属性を使用すると有効になります。
#![doc(html_playground_url = "https://play.rust-lang.org/")]
//! ```
//! println!("Hello World");
//! ```