Rust by Example
Rust は安全性、速度、並列性にフォーカスした現代的なシステムプログラミング用のプログラミング言語です。ガベージコレクション無しでメモリ安全であることが、これを可能にしています。
Rust by Example(RBE)はRustの実行可能なサンプルスクリプト集で、ここではRustの様々なコンセプトと標準ライブラリを紹介していきます。この例をより活用するためにはRustをローカルにインストールし、公式ドキュメントをチェックすることをおすすめします。興味がある方はこのサイト自体のソースのチェックもどうぞ。
それでははじめましょう!
-
Hello World - お決まりのHello Worldプログラムから始めましょう。
-
基本データ型 - 符号付き整数や符号無し整数、その他の基本データ型について学びましょう。
-
カスタム型 -
struct
とenum
について。 -
変数の束縛 - ミュータブルな束縛、スコープ、シャドーイングについて。
-
型 - 型を変更したり定義したりすることを学びましょう。
-
型変換 - 文字列や整数、浮動小数点数など様々な型から型への変換について。
-
式 - 式とその使い方について学びましょう。
-
制御フロー -
if
やelse
、for
など。 -
関数 - メソッド、クロージャ、高階関数について。
-
モジュール - プログラムをモジュールを使って整理しましょう。
-
クレート - クレートは、Rustにおいてコンパイルされる単位です。ライブラリの作り方について学びます。
-
Cargo - Rustの公式パッケージマネージャの基本的な機能を学びます。
-
アトリビュート - アトリビュートは、モジュールやクレート、要素に適用されるメタデータです。
-
ジェネリクス - 様々な型の引数を取れる関数やデータ型を書く方法を学びましょう。
-
スコープの規則 - スコープは所有権、借用、ライフタイムにおいて重要な役割を果たします。
-
トレイト - トレイトとは、未知の型
Self
に対して定義された一連のメソッドです。 -
マクロ - マクロはコードを書くためのコードです。メタプログラミングとしても知られています。
-
エラーハンドリング - 失敗に対処するRust流のやり方を学びましょう。
-
標準ライブラリの型 -
std
ライブラリによって提供されるいくつかのカスタム型について学びます。 -
標準ライブラリのその他 - ファイルハンドリングとスレッドのためのカスタム型について。
-
テスト - Rustにおけるテストのすべて。
-
安全でない操作 - 安全でない操作について学びましょう。
-
互換性 - Rustの進化と互換性について。
-
周辺情報 - ドキュメント、ベンチマークの方法。
Hello World
ここでは伝統的な "Hello World!" プログラムのソースを紹介します。
// これはコメントです。コンパイラによって無視されます。 // 右にある「Run」ボタンからこのコードをテストできます。 // キーボードを使いたければ「Ctrl + Enter」もOKです。 // このコードは編集可能です。ぜひハックしてみましょう! // 「Reset」ボタンでいつでも元のコードに戻すことができます -> // main関数です。 fn main() { // コンパイルされたバイナリが実行されるとこの関数が呼び出されます。 // コンソールに文字列を出力します。 println!("Hello World!"); }
println!
は文字列をコンソールに出力するための マクロ です。
バイナリファイルはrustc
と呼ばれるRustコンパイラを用いて生成することができます。
$ rustc hello.rs
するとhello
という名前の実行可能なバイナリファイルができます。
$ ./hello
Hello World!
演習
上に書いている 'Run' をクリックしてアウトプットを見てみましょう。次に、println!
マクロをもう一行追加してアウトプットがどうなるか見てみましょう。
Hello World!
I'm a Rustacean!
コメント
あらゆるプログラムにはコメントが必要です。Rustには何種類かのコメントがあります
- 通常のコメント これはコンパイラによって完全に無視されます。
// 行末までコメントアウト
/* ブロックによって囲まれた部分をコメントアウト */
- ドキュメンテーションコメント ライブラリのドキュメンテーションとしてHTMLにパースされます。
/// このコメントの下の内容に関するドキュメントとなります。
//! このコメントを含むソースのドキュメントになります。
fn main() { // こちらはラインコメントです。 // 一番左にスラッシュが2つある行と、何も書かれていない行は // どちらもコンパイラによって無視されます。 // println!("Hello, world!"); // でしょ?では次に、左のスラッシュを消去してから実行してください。 /* * こちらはもう一つのタイプのコメントでブロックコメントと呼ばれます。 * 普通はラインコメントの方が優れているのですが、こちらはコード片を * 一時的に無効にするときに役立つかもしれません。 * /* ブロックコメントは /* ネストすることができるので */ */ * わずかなキー入力でこのmain関数全体をコメントアウトできます。 * /*/*/* 試してみてください! */*/*/ */ /* 注: 上のコメントにおける`*`列は見栄えのためでした。実際には必要ありません。 */ // ではブロックコメントがどのようにデバッグに役立つか見てみましょう。 // 例えば下の例の場合、ブロックコメントがなくなれば結果が変わります。 let x = 5 + /* 90 + */ 5; println!("Is `x` 10 or 100? x = {}", x); }
参照
フォーマットして出力
Printing is handled by a series of macros
defined in std::fmt
some of which are:
format!
:フォーマットされたテキストをString
に書き込みます。print!
:format!
と同様ですが、コンソール (io::stdout) にそのテキストを出力します。println!
:print!
と同じですが改行が付け加えられます。eprint!
:format!
と同様ですが、標準エラー出力 (io::stderr) にそのテキストを出力します。eprintln!
:eprint!
と同じですが改行が付け加えられます。
すべて同じやり方でテキストをパースし、正しくフォーマットできるかコンパイル時にチェックします。
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" になります。 // (4つの空白と"1"で合計幅は5です) println!("{number:>5}", number=1); // 数字を0埋めすることもできます。 println!("{number:0>5}", number=1); // 00001 // 記号を反対にすると左寄せになり、"10000"が出力されます。 println!("{number:0<5}", number=1); // 10000 // フォーマット指定子の中に`$`をつけることで名前付き引数を利用できます。 println!("{number:0>width$}", number=1, width=5); // 引数の数が正しいかのチェックも行ってくれます。 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以上では、周囲の変数から直接引数に取ることができます。 // 上で見たように、以下のコードは4つのスペースと1を、" 1" と出力します。 let number: f64 = 1.0; let width: usize = 5; println!("{number:>width$}"); }
std::fmt
はいくつものトレイトを持ち、それによってどのようにディスプレイに表示されるかが決まります。特に大事な形式は以下の2つです。
fmt::Debug
:{:?}
というマーカーを使用し、デバッグ目的に使われます。fmt::Display
:{}
というマーカーを使用し、より美しく、ユーザフレンドリーに表示します。
この例で用いられている型は、標準ライブラリに含まれているため、ここではfmt::Display
を使用しています。カスタム型をテキストとして表示する場合は、さらに手順が必要です。
fmt::Display
トレイトを実装すると、自動的にToString
トレイトが実装されます。これによりString
型への型変換ができるようになります。
43行目 の#[allow(dead_code)]
は、直後のモジュールにのみ適用されるアトリビュートです。
演習
- 上の例(FIXME を参照)を実行した際に生じるエラーを修復しましょう。
Structure
構造体をフォーマットする行をアンコメントしてみましょう。(TODO を参照)println!
マクロを追加し、表示される小数部の桁数を調整してPi is roughly 3.142
という文字列を出力しましょう。ただし、円周率の値はlet pi = 3.141592
を使ってください。(ヒント:小数部の桁数を調整する方法については、std::fmt
をチェックする必要があるかもしれません。)
参照
std::fmt
, マクロ, 構造体, トレイト, dead_code
Debug
std::fmt
のフォーマット用トレイト
を使用したい型は、出力できるように実装されている必要があります。std
ライブラリの型のように自動で出力可能なものもありますが、他はすべて 手動で実装する必要があります。
fmt::Debug
というトレイト
はこれを簡略化します。 すべての 型はfmt::Debug
の実装を導出(derive)
、(すなわち自動で作成)することができるためです。fmt::Display
の場合はやはり手動で実装しなくてはなりません。
#![allow(unused)] fn main() { // この構造体は`fmt::Display`、`fmt::Debug`のいずれによっても // 出力することができません。 struct UnPrintable(i32); // `derive`アトリビュートは、 // この構造体を`fmt::Debug`で出力するための実装を自動で提供します。 #[derive(Debug)] struct DebugPrintable(i32); }
std
ライブラリの型の場合は、自動的に{:?}
により出力可能になっています。
// `Structure`という構造体のための`fmt::Debug`を導出しています。 // `Structure`は単一の`i32`をメンバに持っています。 #[derive(Debug)] struct Structure(i32); // `Deep`という構造体の中に`Structure`を入れ、これを出力可能にしています。 #[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
を実装することで出力結果を思い通りにできます。
参照
アトリビュート, derive
, std::fmt
, 構造体
Display
fmt::Debug
はコンパクトでクリーンであるようには見えませんね。大抵の場合は、アウトプットの見た目をカスタマイズしたほうが好ましいでしょう。これは{}
を使用するfmt::Display
を手動で実装することで可能です。実装はこのようになります。
#![allow(unused)] fn main() { // (`use`を使用し、)`fmt`モジュールをインポートします。 use std::fmt; // `fmt::Display`を実装するための構造体を定義します。 // これは`Structure`と名付けられた、`i32`を含むタプル構造体です。 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
(,
で分割)
答えはNOです。あらゆる型に対して理想的なスタイルなどというものはありませんし、std
ライブラリによってそれが提供されているわけでもありません。fmt::Display
はVec<T>
のようなジェネリックなコンテナ用に定義されているわけではありませんので、このような場合はfmt::Debug
を使用するべきです。
ジェネリック でない コンテナ型の場合は、このような問題は生じませんので問題なくfmt::Display
を実装することができます。
use std::fmt; // `fmt`のインポート // 2つの数字を扱うための構造体です。出力を`Display`と比較するため`Debug` // を導出しています。 #[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`は実装されていますが、`fmt::Binary`はされていないため // `{:b}`使用している以下の例はエラーになります。 // println!("What does Point2D look like in binary: {:b}?", point); }
fmt::Display
は実装されていますが、fmt::Binary
はされていないので使用できません。std::fmt
にはそのようなトレイトが数多くあり、それぞれに独自の実装が必要です。詳しくはstd::fmt
を参照してください。
演習
上記の例のアウトプットを確認し、Point2D
構造体を参考として、複素数を格納するためのComplex
構造体を定義しましょう。うまく行けば以下のように出力されるはずです。
Display: 3.3 + 7.2i
Debug: Complex { real: 3.3, imag: 7.2 }
参照
導出, std::fmt
, マクロ, 構造体, トレイト, use
テストケース:リスト
構造体のそれぞれの要素を別々に扱うfmt::Display
を実装するのはトリッキーです。というのも、それぞれのwrite!
が別々のfmt::Result
を生成するためです。適切に処理するためには すべての 結果に対して処理を書かなくてはなりません。このような場合は?
演算子が使えます。
以下のように?
を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 { // タプルインデックスを使って値を取り出し、それへの参照`vec`を作ります。 let vec = &self.0; write!(f, "[")?; // `v`を介して`vec`をイテレートし、同時にカウントを // `enumerate`で取得します。 for (count, v) in vec.iter().enumerate() { // 先頭以外の全要素にカンマを付けます。 // ?演算子を使ってエラーを返します。 if count != 0 { write!(f, ", ")?; } write!(f, "{}", v)?; } // 開きっぱなしのブラケットを閉じて、`fmt::Result`の値を返します。 write!(f, "]") } } fn main() { let v = List(vec![1, 2, 3]); println!("{}", v); }
演習
上記のプログラムを変更して、ベクタの各要素のインデックスも表示するようにしてみましょう。変更後の出力は次のようになります。
[0: 1, 1: 2, 2: 3]
参照
for
, ref
, Result
, 構造体, ?
, vec!
フォーマット
これまで、文字列がどのようにフォーマットされるかは フォーマット文字列 によって決まるということを見てきました 。
format!("{}", foo)
->"3735928559"
format!("0x{:X}", foo)
->"0xDEADBEEF"
format!("0o{:o}", foo)
->"0o33653337357"
ここでは(foo
)という単一の変数がX
、o
、 指定なし 、という様々な 引数タイプ に応じてフォーマットされています。
フォーマットの機能はそれぞれの引数タイプごとに個別のトレイトを用いて実装されています。最も一般的なトレイトはDisplay
で、これは引数タイプが未指定(たとえば{}
)の時に呼び出されます。
use std::fmt::{self, Formatter, Display}; struct City { name: &'static str, // 緯度 lat: f32, // 経度 lon: f32, } impl Display for City { // `f`はバッファです。このメソッドは // ここにフォーマットされた文字列を書き込みます。 fn fmt(&self, f: &mut Formatter) -> fmt::Result { let lat_c = if self.lat >= 0.0 { 'N' } else { 'S' }; let lon_c = if self.lon >= 0.0 { 'E' } else { 'W' }; // `write!`は`format!`に似ていますが、フォーマットされた文字列を // バッファ(第一引数)に書き込みます。 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
詰まったら以下の3つがヒントになります。
- RGB色空間で色を計算する式は
RGB = (R*65536)+(G*256)+B (R は RED, G は GREEN and B は BLUE)
です。詳細はRGB color format & calculationを参照。 - それぞれの色を2回以上記述する必要があるかもしれません。
:0>2
で、幅を2に指定し、空白を0で埋める事ができます。
参照
基本データ型
Rustは様々な基本データ型の使用をサポートしています。以下がその例です。
スカラー型
- 符号付き整数:
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; /* Compound types - Array and Tuple */ // Array signature consists of Type T and length as [T; length]. let my_array: [i32; 5] = [1, 2, 3, 4, 5]; // Tuple is a collection of values of different types // and is constructed using parentheses (). let my_tuple = (5u32, 1u8, true, -5.04f32); }
参照
リテラルと演算子
整数1
、浮動小数点数1.2
、文字'a'
、文字列"abc"
、ブーリアンtrue
、ユニット()
は、リテラルを使って表すことが可能です。
また整数型の場合、プレフィックスに0x
、0o
、0b
を指定することでそれぞれ16進数、8進数、2進数を使って表すことができます。
可読性のため、_
(アンダースコア)を数値リテラルの間に挿入することができます。例えば1_000
は1000
と、0.000_001
は0.000001
とそれぞれ同一です。
また、Rustは1e6
や7.6e-4
などの科学的なE表記をサポートしています。この表記は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, ...)
のようになります。タプルは大きさに制限がありませんので、関数が複数の値を返したい時に使われます。
// タプルを関数の引数と返り値として使用しています。 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
関数を雛形にしたtranspose
関数を実装してください。この関数はMatrix
を引数として受け取り、要素のうち2つを入れ替えたものを返します。つまり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!("First element of the slice: {}", slice[0]); println!("The slice has {} elements", slice.len()); } fn main() { // 固定長の配列(型シグネチャは冗長なので、なくても可) let xs: [i32; 5] = [1, 2, 3, 4, 5]; // すべての要素を同じ値で初期化する場合 let ys: [i32; 500] = [0; 500]; // インデックスは0から。 println!("First element of the array: {}", xs[0]); println!("Second element of the array: {}", xs[1]); // `len`は配列の要素数を返します。 println!("Number of elements in array: {}", xs.len()); // 配列はスタック上に置かれます。 println!("Array occupies {} bytes", mem::size_of_val(&xs)); // 配列は自動的にスライスとして借用されます。 println!("Borrow the whole array as a slice."); analyze_slice(&xs); // スライスは配列の一部を指すことができます。 // [starting_index..ending_index] の形をとり、 // `starting_index`はスライスの先頭の位置を表し、 // `ending_index`はスライスの末尾の1つ先の位置を表します。 println!("Borrow a section of the array as a slice."); analyze_slice(&ys[1 .. 4]); // 空のスライス`&[]`の例: let empty_array: [u32; 0] = []; assert_eq!(&empty_array, &[]); assert_eq!(&empty_array, &[][..]); // 同じ意味だがより冗長な書き方 // 配列は、`Option`を返す`.get`で安全にアクセスできます。 // `Option`は以下のようにマッチさせることもできるし、 // 運よく処理を続ける代わりに、`.expect()`で素敵なメッセージとともに // プログラムを終了することもできます。 for i in 0..xs.len() + 1 { // おっと、1要素余分! match xs.get(i) { Some(xval) => println!("{}: {}", i, xval), None => println!("Slow down! {} is too far!", i), } } // 配列のインデックスが範囲外のときはコンパイルエラー。 //println!("{}", xs[5]); // スライスのインデックスが範囲外のときはランタイムエラー。 //println!("{}", xs[..][5]); }
カスタム型
Rustでのカスタムデータ型の作成は主に以下の2つのキーワードを介して行われます。
struct
:構造体を定義するenum
:列挙型を定義する
const
、あるいはstatic
というキーワードによって定数を定義することもできます。
構造体
struct
というキーワードを用いて作成できる構造体には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); // 構造体の更新記法を用いて、別の構造体のフィールドの値を基に // 新たなpointを生成。 let bottom_right = Point { x: 10.3, ..another_point }; // `bottom_right.y`の値は`another_point.y`と同一ですが、 // これは`another_point`のフィールドの値を用いて生成したためです。 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
を引数とし、Rectangle
を返すsquare
関数を追加してください。Rectangle
の左上の点がPoint
になり、f32
がRectangle
の幅と高さになります。
参照
列挙型
列挙型(enum
)はいくつかの異なる要素型の中から1つを選ぶような場合に使用します。構造体(struct
)の定義を満たすものならば何でもenum
の要素型として使用できます。
// `enum`を作成してwebイベントを分類します。 // 名前と型情報を併せたものが要素型になっていることに注意。 // `PageLoad != PageUnload`、 // `KeyPress(char) != Paste(String)`です。 // 要素型は互いに異なり、互いに非依存です。 enum WebEvent { // `enum`要素型はユニット風でも構いません。 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; }
このやり方がもっともよく見られるのは、impl
ブロックでSelf
という別名を使用する場合です。
enum VeryVerboseEnumOfThingsToDoWithNumbers { Add, Subtract, } impl VeryVerboseEnumOfThingsToDoWithNumbers { fn run(&self, x: i32, y: i32) -> i32 { match self { Self::Add => x + y, Self::Subtract => x - y, } } }
列挙型や型エイリアスについて詳しく学びたい人は、この機能が安定してRustに取り込まれたときのstabilization reportを読んでください。
参照
match
, 関数, 文字列, "Type alias enum variants" RFC
use
use
を使用すれば変数のスコープを絶対名で指定する必要がなくなります。
// 使用されていないコードよる警告を隠すアトリビュート #![allow(dead_code)] enum Stage { Beginner, Advanced, } enum Role { Student, Teacher, } fn main() { // `use`することで絶対名でなくとも使用可能になります。 use crate::Stage::{Beginner, Advanced}; // `Role`の中の名前をすべて`use`します。 use crate::Role::*; // `use`しているため、`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言語ライクな列挙型
列挙型はC言語の列挙型のような使い方をする事もできます。
// 使用されていないコードよる警告を隠すアトリビュート #![allow(dead_code)] // 値を明示しない場合、0から整数が順に入ります。 enum Number { Zero, One, Two, } // 値を明示する場合 enum Color { Red = 0xff0000, Green = 0x00ff00, Blue = 0x0000ff, } fn main() { // 列挙型の中身は整数としてキャストできます。 println!("zero is {}", Number::Zero as i32); println!("one is {}", Number::One as i32); println!("roses are #{:06x}", Color::Red as i32); println!("violets are #{:06x}", Color::Blue as i32); }
参照
テストケース:連結リスト
enum
の使用が適切なパターンのひとつに、連結リストを作成する場合があります。
use crate::List::*; enum List { // Cons:要素をラップし、次の要素へのポインタを保持するタプル構造体 Cons(u32, Box<List>), // Nil:連結リストの終端であることを示すノード Nil, } // 列挙型にはメソッドを付与することができます。 impl List { // 空リストの作成 fn new() -> List { // `Nil`は`List`型を持ちます。 Nil } // リストを受け取り、その始端に新しい要素を付加したものを返す関数 fn prepend(self, elem: u32) -> List { // この`Cons`自体も、その第2要素もどちらもList型です。 Cons(elem, Box::new(self)) } // リストの長さを返すメソッド fn len(&self) -> u32 { // このメソッドは、`self`の状態によって振る舞いが // 変化するため、matchをする必要があります。 // `self`の型は`&List`なので、`*self`は`List`になります。マッチングは // 参照(`&T`)ではなく実体(`T`)に対して行うのが好ましいです。 // Rust 2018以降ではここでselfと(refのない)tailを使うことができ、 // &selfとref tailが推論されます。 // 参照 https://doc.rust-lang.org/edition-guide/rust-2018/ownership-and-lifetimes/default-match-bindings.html match *self { // `self`をすでに借用しているので、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!("linked list has length: {}", list.len()); println!("{}", list.stringify()); }
参照
定数
Rustには2種類の定数があり、いずれもグローバルスコープを含む任意のスコープで宣言することができます。また、いずれも型を明示しなくてはなりません。
const
:不変の値(通常はこちらを使用します)static
:'static
ライフタイムを持つ変更可能な値。スタティックライフタイムは推論され、明示する必要はありません。可変なスタティック値へのアクセスや変更は安全ではありません。
// グローバル変数はあらゆるスコープの外で宣言します。 static LANGUAGE: &str = "Rust"; const THRESHOLD: i32 = 10; fn is_big(n: i32) -> bool { // 関数内から定数を参照。 n > THRESHOLD } fn main() { let n = 16; // main 関数の中から定数を参照。 println!("This is {}", LANGUAGE); println!("The threshold is {}", THRESHOLD); println!("{} is {}", n, if is_big(n) { "big" } else { "small" }); // エラー!const`は変更できません。 THRESHOLD = 5; // FIXME ^ この行をコメントアウトしましょう }
参照
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); // 問題ありません。 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は基本データ型について暗黙的な型変換(coerction
)を行うことはありません。しかし明示的な型変換(casting
)は可能です。その場合as
キーワードを使用します。
整数型から整数型へ型変換する場合、C言語で可能なケースの場合はC言語と同じです。C言語で未定義の場合の挙動も、Rustでは完全に定義されています。
// オーバーフローを起こすようなキャストによる警告を無視します。 #![allow(overflowing_literals)] fn main() { let decimal = 65.4321_f32; // エラー!暗黙的な型変換はできません。 let integer: u8 = decimal; // FIXME ^ この行をコメントアウトしましょう // 明示的な型変換 let integer = decimal as u8; let character = integer as char; // エラー!変換ルールには制限があります。 // 浮動小数点数を文字に直接変換することはできません。 let character = decimal as char; // FIXME ^ この行をコメントアウトしましょう println!("Casting: {} -> {} -> {}", decimal, integer, character); // 何らかの値を符号なしの型(仮にTとする)へキャストすると // 値がTに収まるまで、T::MAX + 1 が加算あるいは減算されます。 // 1000はすでにu16に収まっているため変化しません。 println!("1000 as a u16 is: {}", 1000 as u16); // 1000 - 256 - 256 - 256 = 232 // 詳しく見てみると、最下位ビットから8bitが保持され、 // 残りの上位ビットが切り取られる形になります。 println!("1000 as a u8 is : {}", 1000 as u8); // -1 + 256 = 255 println!(" -1 as a u8 is : {}", (-1i8) as u8); // 正の数では、これは剰余と同じです。 println!("1000 mod 256 is : {}", 1000 % 256); // 符号付きの型にキャストする場合、(ビットとして見た)結果は対応する // 符号無し型へのキャストを行った結果と同じです。 // 最上位ビットが1であれば、その値は負であることを示しています。 // すでに収まっている場合はそのままです。 println!(" 128 as a i16 is: {}", 128 as i16); // 境界値のケースを考えると、128の8ビットにおける2の補数は -128です。 println!(" 128 as a i8 is : {}", 128 as i8); // 上で示した例を繰り返すと // 1000 as u8 -> 232 println!("1000 as a u8 is : {}", 1000 as u8); // 232の8ビットにおける補数は -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 { // 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
クレート 内で定義されています。より詳しくはモジュールとクレートを参照してください。
型推論
Rustの型推論エンジンはなかなか賢くできています。初期化の際に評価値の型をチェックするだけでなく、その後にどのような使われ方をしているかを見て推論します。以下がその例です。
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
との相互の型変換では、特殊なトレイトが使用されます。
From
とInto
From
トレイトとInto
トレイトは本質的に結びついており、そのことが実際の実装に反映されています。もし型Aが型Bからの型変換をサポートしているのであれば、型Bは型Aへの型変換ができると思うのが自然です。
From
From
トレイトは、ある型に対し、別の型からその型を作る方法を定義できるようにするものです。そのため、複数の型の間で型変換を行うための非常にシンプルな仕組みを提供しています。標準ライブラリでは、基本データ型やよく使われる型に対して、このトレイトが多数実装されています。
例えば、str
からString
への型変換は簡単です。
#![allow(unused)] fn main() { let my_str = "hello"; let my_string = String::from(my_str); }
We can do something similar for defining a conversion for our own type.
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
and Into
are interchangeable
From
と Into
は相補的にデザインされているので両方のトレイトを実装する必要はありません。From
トレイトを実装すれば、 Into
は必要なときに呼ばれます。ただし、逆は真ではない点に注意してください。 Into
が実装されていても、 From
が自動的に提供されるわけではありません。
use std::convert::From; #[derive(Debug)] struct Number { value: i32, } // Define `From` impl From<i32> for Number { fn from(item: i32) -> Self { Number { value: item } } } fn main() { let int = 5; // use `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への型変換
To convert any type to a String
is as simple as implementing the ToString
trait for the type. Rather than doing so directly, you should implement the fmt::Display
trait which automatically provides ToString
and also allows printing the type as discussed in the section on 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()); }
Stringの解析
文字列からの型変換において、数値への型変換はよく行われるものの一つです。これを行うイディオムはparse
関数を使用することですが、このときに型を推論できるようにするか、もしくはターボフィッシュ構文(::<>
)を使用して型を指定するかのいずれかを行います。以下の例では、どちらの方法も紹介しています。
This will convert the string into the type specified as long as the FromStr
trait is implemented for that type. This is implemented for numerous types within the standard library.
fn main() { let parsed: i32 = "5".parse().unwrap(); let turbo_parsed = "10".parse::<i32>().unwrap(); let sum = parsed + turbo_parsed; println!("Sum: {:?}", sum); }
To obtain this functionality on a user defined type simply implement the FromStr
trait for that type.
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() { // statement // statement // statement }
文にはいくつかの種類があります。最も一般的なのは変数の束縛と;
付きの式です。
fn main() { // 変数束縛 let x = 5; // 式; x; x + 1; 15; }
コードブロックも式の一種です。よってブロックを丸ごと値として扱うことができます。その場合ブロック内の最後の式が場所を表す式(例えばローカル変数)に代入されます。ただし、ブロック内の最後の式が;
で終わる場合は返り値は()
になります。
fn main() { let x = 5u32; let y = { let x_squared = x * x; let x_cube = x_squared * x; // この式は`y`に代入されます。 x_cube + x_squared + x }; let z = { // セミコロンがあるので`z`には`()`が入ります。 2 * x; }; println!("x is {:?}", x); println!("y is {:?}", y); println!("z is {:?}", z); }
制御フロー
処理の流れをコントロールすることはあらゆるプログラミング言語において重要な要素です。if
/else
、for
等です。Rustの文法を見ていきましょう。
if/else
if
-else
を用いた条件分岐は他の言語に似ています。多くの言語では条件式の中を括弧でくくる必要がありますが、Rustではその必要はありません。条件式の直後にはブロックが続きます。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!("Let's count until infinity!"); // 無限ループ loop { count += 1; if count == 3 { println!("three"); // 残りの処理をスキップ。 continue; } println!("{}", count); if count == 5 { println!("OK, that's enough"); // ループを抜ける。 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が返す値
loop
の用途のひとつに「成功するまである処理を再試行する」ことがあります。もしその処理が値を返すならば、それをコードの他の部分に渡す必要があるでしょう。break
の後に値を置くと、それがloop
式の値として返されます。
fn main() { let mut counter = 0; let result = loop { counter += 1; if counter == 10 { break counter * 2; } }; assert_eq!(result, 20); }
while
while
キーワードは条件が真である限り実行され続けるループのために使用します。
悪名高いFizzBuzz問題をwhile
を用いて解いてみましょう。
fn main() { // カウンタとなる変数 let mut n = 1; // `n`が101より小さい場合のループ while n < 101 { if n % 15 == 0 { println!("fizzbuzz"); } else if n % 3 == 0 { println!("fizz"); } else if n % 5 == 0 { println!("buzz"); } else { println!("{}", n); } // カウンタに1を追加。 n += 1; } }
forループ
for と range
for in
構文を用いることで、イテレータのそれぞれの要素に対して処理をすることが可能です。イテレータを作る最も単純な方法はa..b
のような範囲記法です。これは「a
」から「b
のひとつ前」までの要素を順に生成します。
ではwhile
の代わりにfor
を用いてFizzBuzzを書いてみましょう。
fn main() { // `n`は1, 2, ...., 100のそれぞれの値を取ります。 for n in 1..101 { if n % 15 == 0 { println!("fizzbuzz"); } else if n % 3 == 0 { println!("fizz"); } else if n % 5 == 0 { println!("buzz"); } else { println!("{}", n); } } }
上記の代わりにa..=b
を用いると、両端の値を含む範囲を指定できます。上記の例は次のように書けます。
fn main() { // `n`は1, 2, ...., 100のそれぞれの値を取ります。 for n in 1..=100 { if n % 15 == 0 { println!("fizzbuzz"); } else if n % 3 == 0 { println!("fizz"); } else if n % 5 == 0 { println!("buzz"); } else { println!("{}", n); } } }
forとイテレータ
for in
構文はイテレータとさまざまな方法でやり取りできます。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 ^ Try deleting the & and matching just "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), } } println!("names: {:?}", names); // FIXME ^ この行をコメントアウトしましょう }
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); }
上記に示した3つのコードにおいて、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; // マッチは式でもあります。 let binary = match boolean { // マッチは全ての可能な値をカバーしなくてはなりません。 false => 0, true => 1, // TODO ^ 試しに片方をコメントアウトしてみましょう。 }; println!("{} -> {}", boolean, binary); }
デストラクト
match
は値をさまざまなやり方でデストラクトすることができます。
タプル
以下のように、タプルはmatch
を用いてデストラクトすることができます。
fn main() { let triple = (0, -2, 3); // TODO ^ `triple`に別の値を入れてみましょう。 println!("Tell me about {:?}", triple); // `match`を用いてタプルをデストラクトしてみましょう。 match triple { // 2つ目と3つ目の要素をデストラクト。 (0, y, z) => println!("First is `0`, `y` is {:?}, and `z` is {:?}", y, z), (1, ..) => println!("First is `1` and the rest doesn't matter"), (.., 2) => println!("last is `2` and the rest doesn't matter"), (3, .., 4) => println!("First is `3`, last is `4`, and the rest doesn't matter"), // `..`を使うと、タプルの残りの部分を無視できます。 _ => println!("It doesn't matter what they are"), // ここでは`_`は、値を変数に束縛しないことを意味します。 } }
参照
配列とスライス
タプル同様、配列とスライスも以下のようにデストラクトできます。
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] = {} and array[1] was ignored", third ), // いくつかの値を束縛して残りを無視できます。 [-1, second, ..] => println!( "array[0] = -1, array[1] = {} and all the other ones were ignored", second ), // 以下のコードはコンパイルできません。 // [-1, second] => ... // 別の配列やスライスに値を持たせることもできます。 // (配列かスライスかは、マッチする値の型により異なります) [3, second, tail @ ..] => println!( "array[0] = 3, array[1] = {} and the other elements were {:?}", second, tail ), // 例えば、これらのパターンを組み合わせて、 // 最初と最後の値を束縛し、残りの値を配列に持たせることもできます。 [first, middle @ .., last] => println!( "array[0] = {}, middle = {:?}, array[2] = {}", first, middle, last ), } }
参照
列挙型
列挙型も似たやり方でデストラクトすることができます。
// `allow`は値を一つだけ使用したことによる警告を抑えるために存在します。 #[allow(dead_code)] enum Color { // これら3つの値は名前のみで扱うことができます。 Red, Blue, Green, // 以下の値は名前と`u32`のタプルをペアにしています。 // カラーモデルと呼ばれます。 RGB(u32, u32, u32), HSV(u32, u32, u32), HSL(u32, u32, u32), CMY(u32, u32, u32), CMYK(u32, u32, u32, u32), } fn main() { let color = Color::RGB(122, 17, 40); // TODO ^ `color`に別の値を入れてみましょう。 println!("What color is it?"); // `enum`は`match`を利用してデストラクトすることができます。 match color { Color::Red => println!("The color is Red!"), Color::Blue => println!("The color is Blue!"), Color::Green => println!("The color is Green!"), Color::RGB(r, g, b) => println!("Red: {}, green: {}, and blue: {}!", r, g, b), Color::HSV(h, s, v) => println!("Hue: {}, saturation: {}, value: {}!", h, s, v), Color::HSL(h, s, l) => println!("Hue: {}, saturation: {}, lightness: {}!", h, s, l), Color::CMY(c, m, y) => println!("Cyan: {}, magenta: {}, yellow: {}!", c, m, y), Color::CMYK(c, m, y, k) => println!("Cyan: {}, magenta: {}, yellow: {}, key (black): {}!", c, m, y, k), // 全ての値を列挙したのでその他の場合の処理は必要ありません。 } }
参照
ポインタとref
Rustのポインタは、C/C++のポインタとは異なる概念なので、デストラクトとデリファレンスを同じようなやり方で扱うことはできません。
- デリファレンスには
*
を用います。 - デストラクトには
&
,ref
,ref mut
を用います。
fn main() { // `i32`型への参照を代入します。 // `&`によって参照であることを明示しています。 let reference = &4; match reference { // 上で定義した`reference`という変数が`&val`とのパターンマッチ // に用いられた場合、以下の2つの値が比較されていることになります。 // `&i32` // `&val` // ^ よって`&`を落とせば、`i32`が`val`に代入されることがわかります。 &val => println!("Got a value via destructuring: {:?}", val), } // `&`を使用したくない場合は、マッチングの前にデリファレンスします。 match *reference { val => println!("Got a value via dereferencing: {:?}", val), } // いきなり参照を変数に代入するのではない場合はどうでしょう。 // 先ほどは右辺値が`&`で始まっていたので参照でしたが、 // これは違います。 let _not_a_reference = 3; // このような場合、Rustでは変数束縛時に`ref`を宣言します。 // 要素の参照が作られて、それが束縛対象になります。 let ref _is_a_reference = 3; // 同様にミュータブルな値の場合`ref mut`を使用することで参照を // 取得できます。イミュータブルの場合と合わせてみていきましょう。 let value = 5; let mut mut_value = 6; // `ref`を使用して参照を作成。 match value { ref r => println!("Got a reference to a value: {:?}", r), } // 同様に`ref mut`を使用。 match mut_value { ref mut m => { // 参照を取得、値を変更するためにはデリファレンスします。 *m += 10; println!("We added 10. `mut_value`: {:?}", m); }, } }
参照
構造体
以下のようにして、構造体も同様にデストラクトすることができます。
fn main() { struct Foo { x: (u32, u32), y: u32, } // 構造体の中の値を変えて、何が起きるか見てみましょう。 let foo = Foo { x: (1, 2), y: 3 }; match foo { Foo { x: (1, b), y } => println!("First of x is 1, b = {}, y = {} ", b, y), // 構造体をデストラクトして変数をリネーム。 // 順番は重要ではありません。 Foo { y: 2, x: i } => println!("y is 2, i = {:?}", i), // 一部の変数を無視することもできます。 Foo { y, .. } => println!("y = {}, we don't care about 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!("Outside: 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: 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`とそれに続く条件式がガードです。 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), } }
パターンが全てカバーされているかどうかを判断する際に、ガード条件は考慮されないことに注意してください。
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
内では@
マークを使用して変数を束縛することができます。
// `age`関数は`u32`の値を返します。 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`させることができます。 // しかしその場合、子供は何歳でしょうか? // マッチした値を`n`に束縛することで値を使用できます。 n @ 1 ..= 12 => println!("I'm a child of age {:?}", n), n @ 13 ..= 19 => println!("I'm a teen of age {:?}", n), // マッチしなかった場合の処理 n => println!("I'm an old person of age {:?}", n), } }
Option
のような、列挙型の値をデストラクトするためにも、束縛を利用できます。
fn some_number() -> Option<u32> { Some(42) } fn main() { match some_number() { // `n`に束縛された`Some`の値が42に等しいときにマッチ。 Some(n @ 42) => println!("The Answer: {}!", n), // それ以外の数値にマッチ。 Some(n) => println!("Not interesting... {}", n), // それ以外にマッチ(`None`の値)。 _ => (), } }
参照
if let
列挙型をマッチさせるとき、場合によってはmatch
を使用すると不自然な書き方になってしまう場合があります。例えば
#![allow(unused)] fn main() { // `optional`という変数の型を`Option<i32>`に指定。 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); } // デストラクトした結果が`Some()`にならない場合の処理を明示したい場合、 // `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 { // 今回は`else if`の評価がfalseなので、このブロック内がデフォルト。 println!("I don't like letters. Let's go with an emoticon :)!"); } }
同様にif let
は任意の列挙型のマッチに使えます。
// 列挙型の例 enum Foo { Bar, Baz, Qux(u32) } fn main() { // 変数の作成 let a = Foo::Bar; let b = Foo::Baz; let c = Foo::Qux(100); // 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 Foo::Qux(value @ 100) = c { println!("c is one hundred"); } }
もう一つのメリットはif let
がパラメータを持たない列挙型にも使えることです。列挙型がPartialEq
を実装または導出していなくても問題ありません。その場合、列挙型のインスタンスは比較できないのでif Foo::Bar == a
はコンパイルエラーとなりますが、if let
は引き続き使えます。
試してみましょう。以下の例をif let
を使って直してみてください。
// この列挙型はPartialEqを実装も導出もしていません。 // そのためFoo::Bar == aはエラーとなります。 enum Foo {Bar} fn main() { let a = Foo::Bar; // 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
式との主な違いです。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() { // `optional`という変数の型を`Option<i32>`に指定。 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() { // `optional`という変数の型を`Option<i32>`に指定。 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`は呼び出し元オブジェクトの型。この場合は`Rectangle`。 fn area(&self) -> f64 { // `self`はドット演算子によって構造体のフィールドを参照できます。 let Point { x: x1, y: y1 } = self.p1; let Point { x: x2, y: y2 } = self.p2; // `abs`は`f64`のメソッドで、呼び出し元の値の絶対値を返します。 ((x1 - x2) * (y1 - y2)).abs() } fn perimeter(&self) -> f64 { let Point { x: x1, y: y1 } = self.p1; let Point { x: x2, y: y2 } = self.p2; 2.0 * ((x1 - x2).abs() + (y1 - y2).abs()) } // このメソッドは呼び出し元オブジェクトがミュータブルであることを // 必要とします。`&mut self`は`self: &mut Self`の糖衣構文です。 fn translate(&mut self, x: f64, y: f64) { self.p1.x += x; self.p2.x += x; self.p1.y += y; self.p2.y += y; } } // `Pair`はヒープ上の整数を2つ保持します。 struct Pair(Box<i32>, Box<i32>); impl Pair { // このメソッドは呼び出し元オブジェクトを「消費」します。 // `self`は`self: Self`の糖衣構文です。 fn destroy(self) { // `self`をデストラクト。 let Pair(first, second) = self; println!("Destroying Pair({}, {})", first, second); // `first`、`second`はスコープから抜け出すと同時に、解放されます。 } } fn main() { let rectangle = Rectangle { // 関連関数はコロンを2つ挟んで呼び出すことができます。 p1: Point::origin(), p2: Point::new(3.0, 4.0), }; // メソッドはドット演算子を用いて呼び出されます。 // 最初の引数`&self`は明示せずに受け渡されていることに注目。つまり // `rectangle.perimeter()` === `Rectangle::perimeter(&rectangle)` println!("Rectangle perimeter: {}", rectangle.perimeter()); println!("Rectangle area: {}", rectangle.area()); let mut square = Rectangle { p1: Point::origin(), p2: Point::new(1.0, 1.0), }; // エラー!`rectangle`はイミュータブルですがこのメソッドは // ミュータブルなオブジェクトを必要とします。 //rectangle.translate(1.0, 0.0); // TODO ^ この行をアンコメントしてみましょう。 // OK!ミュータブルなオブジェクトはミュータブルなメソッドを呼び出せます。 square.translate(1.0, 1.0); let pair = Pair(Box::new(1), Box::new(2)); pair.destroy(); // エラー!先ほどの`destroy`で`pair`はすでに消費されてしまっています。 //pair.destroy(); // TODO ^ この行をアンコメントしてみましょう。 }
クロージャ
Rustにおけるクロージャは、その外側の環境を捕捉した関数のことです。例えば、次のコードは変数x
を捕捉したクロージャです。
|val| val + x
クロージャの構文や機能は、その場限りの用途で何かを作るのに便利です。クロージャの呼び出しは関数の呼び出しと全く同じです。しかし、入力の型と戻り値の型は推論させることができますが、入力変数の名前は必ず指定しなくてはなりません。
クロージャの他の特徴を以下に示します。
- 入力変数を囲むのに、
()
の代わりに||
を用います。 - 本体が単一の式の場合は、本体の区切り文字(
{}
)を省略できます。(それ以外の場合は必須です) - 外側の環境にある変数を捕捉することができます。
fn main() { 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!("cannot reuse closure_inferred with another type: {}", closure_inferred(42i64)); // TODO: 上の行をアンコメントしてコンパイルエラーを見てみましょう。 // 引数なしで`i32`を返すクロージャ。 // 戻り値の型は推論されます。 let one = || 1; println!("closure returning one: {}", one()); }
要素の捕捉
クロージャはとてもフレキシブルに動作するように出来ています。クロージャにおいて型アノテーションをする必要が無いのは前述の仕組みのためですが、この仕組みのおかげでユースケースに応じて参照を取得したり値そのものを取得したりといった動作が可能になります。クロージャは外側の環境にある要素を、以下の形で取得することができます。
- 参照:
&T
- ミュータブルな参照:
&mut T
- 値そのもの:
T
クロージャは出来る限り参照を取得しようとし、その他2つは必要なときのみ取得します。
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`をインクリメントするためのクロージャ。`count`と`&mut count` // の両方を取ることができますが、後者のほうが制限が少ないため、 // (訳注: `count`だと`&mut count`と違い、一度しか呼ぶことができない。) // そちらを取ります。直後に`count`を借用します。 // // `inc`には`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`(ジェネリック型)を取る必要があるため、このクロージャは // 参照ではなく値を取ります。その場合、もしもコピー可能な値ならば、 // 元の値はそのままでコピーのみを取ります。 // 不可能ならば値そのものを移動させます。 let consume = || { println!("`movable`: {:?}", movable); mem::drop(movable); }; // `consume`は変数を消費(開放)するため、一度しか呼び出すことができません。 consume(); // consume(); // ^ TODO: この行のコメントアウトを解除しましょう。 }
バーティカルパイプ(訳注:縦線記号||
)の前にmove
を使用することで、キャプチャする変数の所有権を取ることをクロージャに強制します。
fn main() { // `Vec`はコピーセマンティクスではない。 let haystack = vec![1, 2, 3]; let contains = move |needle| haystack.contains(needle); println!("{}", contains(&1)); println!("{}", contains(&4)); // println!("There're {} elements in vec", haystack.len()); // ^ 上の行のコメントアウトを解除すると、コンパイル時エラーになります。 // これは変数の所有権が移った後の再利用を借用チェッカーが許可しないからです。 // クロージャのシグネチャから`move`を削除すると、クロージャは // _haystack_ 変数をイミュータブルで借用するようになります。 // そのため _haystack_ はまだ利用可能となり、上の行のコメントアウトを // 解除してもエラーが発生しなくなります。 }
参照
捕捉時の型推論
Rustはたいていの場合、型アノテーションなしでも変数を捕捉する方法を臨機応変に選択してくれますが、関数を書く場合にはこの曖昧さは許されません。引数のパラメータとしてクロージャを取る場合、そのクロージャの完全な型はいくつかのtraits
の中の1つを使って明示されなければなりません。どれが使われるかは、捕捉された値でクロージャが何をするかによって決まります。制限の少ない順に並べると、下記の通りです。
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`。 F: Fn(i32) -> i32 { f(3) } fn main() { use std::mem; let greeting = "hello"; // コピーではなくmoveが起きる型 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"); // `mem::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(); } }
クロージャが定義されると、コンパイラは裏側で、無名の構造体を作り、そこにクロージャによって使用される外側の変数を入れます。同時にFn
、FnMut
、FnOnce
という名のトレイトのいずれか一つを介してこの構造体に関数としての機能を実装し、実際に呼び出されるまで待ちます。
この無名構造体は型が未指定なため、関数を実行する際にはジェネリクスが必要とされます。とはいえ、<T>
で指定するだけでは、まだ曖昧です。(訳注:&self
、&mut self
、self
のいずれをとるのかがわからないため)そのため、Fn
、FnMut
、FnOnce
のいずれか一つを実装することで対応しています。
// `F`は引数と戻り値を持たないクロージャ`Fn`を実装していなくてはなりません。 // これはまさに`print`に必要とされるものです。 fn apply<F>(f: F) where F: Fn() { f(); } fn main() { let x = 7; // `x`を無名の構造体に入れ、それに対し`Fn`を実装します。 // (訳注: ここでは`Fn`は`fn Fn(&self) -> {println!("{}", &self)}`) // その構造体を`print`に代入します。 let print = || println!("{}", x); apply(print); }
参照
関数を受け取る関数
これまで、クロージャを引数として渡せることを見てきました。すると次の疑問が浮かんできます。「クロージャではない普通の関数を引数として渡すことは可能なのだろうか?」可能です!もしパラメータとしてクロージャを取る関数を定義すれば、そのクロージャのトレイト境界を満たす任意の関数をパラメータとして渡すことができます。
// 関数を引数として取り、即座に実行する関数を定義。 fn call_me<F: Fn()>(f: F) { f(); } // `Fn`境界を満たすラッパ関数を定義。 fn function() { println!("I'm a function!"); } fn main() { // `Fn`境界を満たすクロージャを定義。 let closure = || println!("I'm a closure!"); call_me(closure); call_me(function); }
クロージャによる変数の捕捉がどのように行われているかを詳しく見たいときはFn
、FnMut
、FnOnce
を参照してください。
参照
クロージャを返す関数
クロージャを引数のパラメータとして用いることができるのと同様に、クロージャを戻り値として返すことも可能です。しかし無名のクロージャの型はその定義上、不明であるため、クロージャを返すためにはimpl Trait
を使用する必要があります。
クロージャを返すために有効なトレイトは下記の通りです。
Fn
FnMut
FnOnce
更に、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`はクロージャが変数を値として取ることを示します。
F: FnMut(Self::Item) -> bool;
}
fn main() { let vec1 = vec![1, 2, 3]; let vec2 = vec![4, 5, 6]; // ベクタ型に対する`iter()`は`&i32`を生成するので、`i32`へとデストラクト。 println!("2 in vec1: {}", vec1.iter() .any(|&x| x == 2)); // `into_iter()`の場合は`i32`を生成するので、デストラクトは必要ありません。 println!("2 in vec2: {}", vec2.into_iter().any(|x| x == 2)); // `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]; // ベクタ型に対する`iter`は`&i32`を生成します。 let mut iter = vec1.iter(); // `inter_iter()`の場合は`i32`を生成します。 let mut into_iter = vec2.into_iter(); // `iter()`は`&i32`を生成し、ここではその参照が必要なので // `&&i32`を`i32`へとデストラクトします。 println!("Find 2 in vec1: {:?}", iter .find(|&&x| x == 2)); // `into_iter()`は`i32`を生成し、ここではその参照が必要なので // `&i32`を`i32`へとデストラクトします。 println!("Find 2 in vec2: {:?}", into_iter.find(| &x| x == 2)); let array1 = [1, 2, 3]; let array2 = [4, 5, 6]; // 配列に対する`iter()`も`&i32`を生成します。 println!("Find 2 in array1: {:?}", array1.iter() .find(|&&x| x == 2)); // 配列に`into_iter()`を使うと`&i32`を生成します。 println!("Find 2 in array2: {:?}", array2.into_iter().find(|&x| x == 2)); }
Iterator::find
は要素への参照を返します。要素の インデックス を使用したい場合、Iterator::position
を使用してください。
fn main() { let vec = vec![1, 9, 3, 3, 13, 2]; // `iter()`は`&i32`を生成しますが`position()`は参照を取らないので // `&i32`を`i32`へとデストラクトします。 let index_of_first_even_number = vec.iter().position(|&x| x % 2 == 0); assert_eq!(index_of_first_even_number, Some(5)); // `into_iter()`は`i32`を生成し、`position()`は参照を取らないので // `デストラクトする必要はありません。 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には高階関数(Higher Order Functions, HOF
)を扱う機能が備わっています。
fn is_odd(n: u32) -> bool { n % 2 == 1 } fn main() { println!("Find the sum of all the numbers with odd squares under 1000"); let upper = 1000; // 宣言型プログラミングによるアプローチ // 値を蓄積する変数を宣言。 let mut acc = 0; // 0から無限までイテレートします。 for n in 0.. { // 値を2乗。 let n_squared = n * n; if n_squared >= upper { // 上限に達した場合、ループを終了。 break; } else if is_odd(n_squared) { // 奇数ならば値を値を足しあわせていきます。 acc += n_squared; } } println!("imperative style: {}", acc); // 関数型プログラミングによるアプローチ let sum_of_squared_odd_numbers: u32 = (0..).map(|n| n * n) // 全自然数を2乗し .take_while(|&n_squared| n_squared < upper) // 上限より小さい値で .filter(|&n_squared| is_odd(n_squared)) // かつ奇数のものを .sum(); // 合計します。 println!("functional style: {}", sum_of_squared_odd_numbers); }
Option と イテレータ には高階関数が使用されています。
発散する関数
発散する関数は決してリターンしない関数です。こうした関数は !
を使って、空の型であることが示されます。
#![allow(unused)] fn main() { fn foo() -> ! { panic!("This call never returns."); } }
他の全ての型と異なり、この型はインスタンス化できません。この型が持ちうる全ての値の集合は空です。この型は()
型とは異なることに注意してください。()
型は値をただ1つだけ持つ型です。
例えば、この関数は通常どおりリターンしますが、戻り値には何の情報も含みません。
fn some_fn() { () } fn main() { let _a: () = some_fn(); println!("This function returns and you can see this line."); }
一方、この関数は呼び出し元に決してリターンしません。
#![feature(never_type)]
fn main() {
let x: ! = panic!("This call never returns.");
println!("You will never see this line!");
}
Although this might seem like an abstract concept, it is actually very useful and often handy. The main advantage of this type is that it can be cast to any other type, making it versatile in situations where an exact type is required, such as in match branches. This flexibility allows us to write code like this:
fn main() { fn sum_odd_numbers(up_to: u32) -> u32 { let mut acc = 0; for i in 0..up_to { // 変数"addition"の型がu32であるため、 // このmatch式はu32をリターンしなければならないことに注意。 let addition: u32 = match i%2 == 1 { // 変数"i"はu32型であるため、全く問題ありません。 true => i, // 一方、"continue"式はu32をリターンしませんが、これでも問題ありません。 // 決してリターンしないため、このmatch式が要求する型に違反しないからです。 false => continue, }; acc += addition; } acc } println!("Sum of odd numbers up to 9 (excluding): {}", sum_odd_numbers(9)); }
この型は、ネットワークサーバのような永遠にループする関数(例:loop {}
)の戻り値の型や、プロセスを終了させる関数(例:exit()
)の戻り値の型としても使用されます。
モジュール
Rustにはコードを階層的に分割し、お互いの機能を隠蔽・公開するための強力なモジュールシステムがあります。
モジュールは関数、構造体、トレイト、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: "public information" }; // フィールドにも普通にアクセスできます。 println!("The open box contains: {}", open_box.contents); // プライベートなフィールドを持つ構造体は、インスタンス化する際に // フィールド名を指定することができません。 // エラー!`ClosedBox`にはプライベートなフィールドがあります。 //let closed_box = my::ClosedBox { contents: "classified information" }; // TODO ^ 試しにここをアンコメントしてみましょう。 // そのような場合でも、パブリックなコンストラクタを介して作成 // することはできます。 let _closed_box = my::ClosedBox::new("classified information"); // パブリックな構造体でも、プライベートなフィールドにはアクセスできません。 // エラー!`contents`フィールドはプライベートです。 //println!("The closed box contains: {}", _closed_box.contents); // TODO ^ ここをアンコメントしてみましょう。 }
参照
use
宣言
use
宣言をすることで、要素の絶対パスを新しい名前に束縛することができ、より簡潔な記述が可能になります。例えば以下のように使えます。
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()`は外の`function()`をシャドウイングします。 use crate::deeply::nested::function; // `use`束縛は局所的なスコープを持ちます。 // この場合には`function()`のシャドーイングはこのブロック内のみとなります。 function(); println!("Leaving block"); } function(); }
super
とself
super
及びself
キーワードは、要素にアクセスする際に、曖昧さをなくし、不必要なハードコーディングを避けるために使用できます。
fn function() { println!("called `function()`"); } mod cool { pub fn function() { println!("called `cool::function()`"); } } mod my { fn function() { println!("called `my::function()`"); } mod cool { pub fn function() { println!("called `my::cool::function()`"); } } pub fn indirect_call() { // `function`という名の様々な関数をこのスコープ内から参照してみましょう。 print!("called `my::indirect_call()`, that\n> "); // `self`キーワードは現在のモジュールスコープを示します。 // この場合は`my`です。`self::function()`と`funcition()`は // 同じ関数であるため、同じ結果になります。 self::function(); function(); // `my`以下の別のモジュールを呼び出す際に // `self`を用いて明示的に参照できます。 self::cool::function(); // `super`は親スコープ(`my`の外側)を参照します。 super::function(); // 以下は *クレート* スコープ内の`cool::function`を束縛します。 // この場合、クレートスコープは一番外側のスコープです。 { 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`によって、`nested.rs`、`inaccessible.rs`
// の内容をこの中で使用することができるようになります。
// 訳注:`pub`をつけないかぎり、この中でしか使用できません。
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」が頭につき、デフォルトでは、その後ろに元となったクレートファイル名をつけます。(訳注: ここではlib
+ rary
)この振る舞いはcrate_name
アトリビュートを用いてオーバーライドできます。
ライブラリの利用
クレートをこの新しいライブラリにリンクするには、rustc
の--extern
フラグを利用します。クレートの要素を全てライブラリと同じ名前のモジュールにインポートします。一般に、このモジュールは他のモジュールと同じように振る舞います。
// extern crate rary; // Rust 2015以前で必要。
fn main() {
rary::public_function();
// エラー!`private_function`はプライベート。
//rary::private_function();
rary::indirect_access();
}
# Where library.rlib is the path to the compiled library, assumed that it's
# in the same directory here:
$ 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" # from crates.io
rand = { git = "https://github.com/rust-lang-nursery/rand" } # from online repo
bar = { path = "../bar" } # from a path in the local filesystem
cargo
は依存管理ツール以上のこともできます。Cargo.toml
のformat specificationに全ての設定オプションがリストアップされています。
プロジェクトをビルドするには、プロジェクトディレクトリのどこか(サブディレクトでも!)でcargo build
を実行します。またcargo run
でビルドと実行をできます。これらのコマンドは、全ての依存関係の解決、必要なクレートのダウンロード、自分のクレートを含む全てのビルドを行うことに注意してください。(make
と同様、まだビルドしていないものだけをビルドします。)
Voila!これで完成です!
規約
前の章ではこのようなディレクトリ階層がありました。
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
注意:Cargoは複数のテストを並列で実行することがありますので、それらが互いに競合しないようにしてください。
並行性が問題を引き起こす一例として、以下のように、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のファイルの1つで、パッケージ内の他のファイルをコンパイルする前にコンパイルされて起動されます。そのため、クレートの前提条件を満たすために使用できます。
Cargoは、ここで指定された環境変数を介してスクリプトに入力を与えます。
スクリプトは標準出力に出力します。出力される行は全て、target/debug/build/<pkg>/output
に書き込まれます。さらに、行頭にcargo:
がついた行はCargoに直接解釈されるため、パッケージのコンパイル時のパラメーターを定義するのに使用できます。
より詳細な仕様や例については、Cargo specificationを参照してください。
アトリビュート
アトリビュートはモジュール、クレート、要素に対するメタデータです。以下がその使用目的です。
- コンパイル時の条件分岐
- クレート名、バージョン、種類(バイナリか、ライブラリか)の設定
- リントの無効化
- コンパイラ付属の機能(マクロ、グロブ、インポートなど)の使用
- 外部ライブラリへのリンク
- ユニットテスト用の関数を明示
- ベンチマーク用の関数を明示
- アトリビュートマクロ
Attributes look like #[outer_attribute]
or #![inner_attribute]
, with the difference between them being where they apply.
-
#[outer_attribute]
applies to the item immediately following it. Some examples of items are: a function, a module declaration, a constant, a structure, an enum. Here is an example where attribute#[derive(Debug)]
applies to the structRectangle
:#![allow(unused)] fn main() { #[derive(Debug)] struct Rectangle { width: u32, height: u32, } }
-
#![inner_attribute]
applies to the enclosing item (typically a module or a crate). In other words, this attribute is interpreted as applying to the entire scope in which it's placed. Here is an example where#![allow(unused_variables)]
applies to the whole crate (if placed inmain.rs
):#![allow(unused_variables)] fn main() { let x = 3; // This would normally warn about an unused variable. }
アトリビュートは以下の様な書き方で引数を取ることができます。
#[attribute = "value"]
#[attribute(key = "value")]
#[attribute(value)]
アトリビュートは複数の値を取ることができ、複数の行に分割することもできます。
#[attribute(value, value2)]
#[attribute(value, value2, value3,
value4, value5)]
dead_code
コンパイラはdead_code
と呼ばれるリント機能を持つため、使用されていない関数が存在するときに警告を出します。アトリビュート によってこの機能を無効化することができます。
fn used_function() {} / `#[allow(dead_code)]`は`dead_code`リントを抑制するアトリビュートです。 #[allow(dead_code)] fn unused_function() {} fn noisy_unused_function() {} // FIXME ^ 警告を抑制するアトリビュートを追加しましょう。 fn main() { used_function(); }
実際のコード中では、使用されていないコードが有る場合はそれを除外するべきです。この文書中では随所でアトリビュートによって警告を抑制していますが、それはあくまでインタラクティブな例を皆さんに提供するためです。
クレート
crate_type
アトリビュートは、そのクレートがライブラリ、バイナリのいずれにコンパイルされるべきかをコンパイラに伝えるために使用します。ライブラリの場合は、どのタイプのライブラリであるかも伝えることができます。crate_name
はクレートの名前を決定するのに使用します。
しかし、crate_type
アトリビュートもcrate_name
アトリビュートも、RustのパッケージマネージャCargoを利用している場合は何の影響もないと知っておくことは重要です。Cargoは大半のRustプロジェクトで利用されており、実世界での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
アトリビュートが使用されているときは、rustc
に--crate-type
フラグを伝える必要はありません。
$ 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!("You are running linux!"); } // そしてこの関数はターゲットOSがLinux *ではない* ときのみコンパイルされます。 #[cfg(not(target_os = "linux"))] fn are_you_on_linux() { println!("You are *not* running linux!"); } fn main() { are_you_on_linux(); println!("Are you sure?"); if cfg!(target_os = "linux") { println!("Yes. It's definitely linux!"); } else { println!("Yes. It's definitely *not* linux!"); } }
参照
条件の追加
target_os
のように、いくつかの条件分岐はrustc
が暗黙のうちに提供しています。条件を独自に追加する場合には--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
はジェネリックな型パラメータに指定されているので、この場所で(arg: T)
のように使用するとジェネリック型として扱われます。これはT
という構造体がそれ以前に定義されていても同様です。
では、手を動かしながらジェネリック型の構文を体験していきましょう。
// `A`という具象型 struct A; // `Single`という型を定義する際に`A`を使用していますが、その最初の使用よりも先に // `<A>`がないため、また、`A`自身も具象型であるため、`Single`は具象型となります。 struct Single(A); // ^ `Single`による`A`の一番最初の使用はここ。 // ここでは`<T>`が一番初めの`T`の使用よりも先に来ています。 // よって`SingleGen`はジェネリック型となります。 // なぜならば型パラメータ`T`がジェネリックだからです。 // `T`はどんな型にもなりえるため、上で定義した`A`を受け取ることもできます。 struct SingleGen<T>(T); fn main() { // `Single`は具象型で、`A`のみを受け取ります。 let _s = Single(A); // `_char`という名の変数を生成します。これは`SingleGen<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) {} // `gen_spec_t`という関数を定義。これは`A`という型を与えられた`SGen<T>` // という型の引数`_s`を取ります。関数名の直後に`<A>`という型パラメータでAが // ジェネリックであることを明示していないので、この関数はAをジェネリック型 // としては取りません fn gen_spec_t(_s: SGen<A>) {} // `gen_spec_i32`という関数を定義。 // これは明示的な型パラメータとして`i32`を与えられた // `SGen<i32>`型の引数`_s`を取ります。 // この関数もジェネリックではありません。 fn gen_spec_i32(_s: SGen<i32>) {} // `generic`という関数を定義。`SGen<T>`という型の引数`_s`を取ります。 // `<T>`が`SGen<T>`に先行しているため、これはTに対してジェネリックな関数です。 fn generic<T>(_s: SGen<T>) {} fn main() { // ジェネリックでない関数を使用します。 reg_fn(S(A)); // 具象型 gen_spec_t(SGen(A)); // 型パラメータ`A`を暗黙のうちに受け取ります。 gen_spec_i32(SGen(6)); // 型パラメータ`i32`を暗黙のうちに受け取ります。 // 型パラメータ`char`を明示的に`generic()`に渡します。 generic::<char>(SGen('a')); // 型パラメータ`char`を暗黙的に`generic()`に渡します。 generic(SGen('c')); }
参照
実装
関数と同様、impl
でメソッドを実装する際にもジェネリック型特有の記法が必要です。
#![allow(unused)] fn main() { struct S; // 具象型`S` struct GenericVal<T>(T); // ジェネリック型`GenericVal` // 型パラメータを指定したうえで、GenericValにメソッドを実装。 impl GenericVal<f32> {} // `f32`の場合のメソッド impl GenericVal<S> {} // 上で定義した`S`への実装 // ジェネリック型のまま扱うには`<T>`が先に来る必要があります。 impl<T> GenericVal<T> {} }
struct Val { val: f64, } struct GenVal<T> { gen_val: T, } // Valに対して実装。 impl Val { fn value(&self) -> &f64 { &self.val } } // ジェネリック型`T`の場合のメソッドをGenValに対して実装。 impl<T> GenVal<T> { fn value(&self) -> &T { &self.gen_val } } fn main() { let x = Val { val: 3.0 }; let y = GenVal { gen_val: 3i32 }; println!("{}, {}", x.value(), y.value()); }
参照
トレイト
もちろんトレイトもジェネリクスを活用することができます。ここではDrop
トレイトをジェネリックメソッドとして再実装し、自身と引数として受け取った値の両方をdrop
するようなメソッドにします。
// コピー不可な型 // 訳注: `clone()`メソッドを用いないかぎり、値のコピーではなくムーブが起きる型 struct Empty; struct Null; // ジェネリック型 `T`に対するトレイト trait DoubleDrop<T> { // `self`に加えてもう一つジェネリック型を受け取り、 // 何もしないメソッドのシグネチャを定義。 fn double_drop(self, _: T); } // `U`を`self`として、`T`をもう一つの引数として受け取る`DoubleDrop<T>` // を実装する。`U`,`T`はいずれもジェネリック型。 impl<T, U> DoubleDrop<T> for U { // このメソッドは2つの引数の所有権を取り、メモリ上から開放します。 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]);
境界のもう一つの効果は、ジェネリック型のインスタンスが、境界条件となるトレイトのメソッドにアクセスすることができるようになる点です。以下がその例です。
// 出力時のマーカー`{:?}`を実装するトレイト 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`トレイトを実装していなくてはなりません。 // その限りにおいて、`T`がどのような具象型であろうとも次の関数は動作します。 fn print_debug<T: Debug>(t: &T) { println!("{:?}", t); } // 「`T`は`HasArea`を実装していなくてはならない」という境界条件を // 満たしていれば、`HasArea`の関数`area`にアクセスできます。 fn area<T: HasArea>(t: &T) -> f64 { t.area() } fn main() { let rectangle = Rectangle { length: 3.0, height: 4.0 }; let _triangle = Triangle { length: 3.0, height: 4.0 }; print_debug(&rectangle); println!("Area: {}", area(&rectangle)); //print_debug(&_triangle); //println!("Area: {}", area(&_triangle)); // ^ TODO: これらの行をアンコメントしてみましょう。 // | Error: `Debug` も `HasArea`もどちらも実装されていません! }
付け加えておくと、where
句を用いて境界を適用することもできます。場合によってはこちらの記法を使用したほうが読みやすくなる場合もあります。
参照
テストケース:空トレイト
トレイト境界の仕組みから、「トレイトがなにも機能を持っていなくとも境界条件として使用できることには変わりはない」という帰結がもたらされます。Eq
とCopy
はstd
ライブラリにおけるそのような例です。
struct Cardinal; struct BlueJay; struct Turkey; trait Red {} trait Blue {} impl Red for Cardinal {} impl Blue for BlueJay {} // 以下の関数はトレイト境界を設けていますが、そのトレイトが空である // か否かとは関係ありません。 fn red<T: Red>(_: &T) -> &'static str { "red" } fn blue<T: Blue>(_: &T) -> &'static str { "blue" } fn main() { let cardinal = Cardinal; let blue_jay = BlueJay; let _turkey = Turkey; // トレイト境界のため、`red`は`blue_jay`に対しては使用できません。 // `blue`と`Cardinal`も同様です。 println!("A cardinal is {}", red(&cardinal)); println!("A blue jay is {}", blue(&blue_jay)); //println!("A turkey is {}", red(&_turkey)); // ^ TODO: この行をアンコメントしてみましょう。 }
参照
std::cmp::Eq
, std::marker::Copy
, トレイト
複数の境界
+
を用いて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
句の方が通常の構文より表現力が高い場合。この例ではwhere
句を使わずに書くことはできません。
use std::fmt::Debug; trait PrintInOption { fn print_in_option(self); } // `where`句を用いない場合、以下と等価な機能を実装するには、 // `T: Debug`という形で表現するか、別の直接的でない方法 // を使用するかしなくてはなりません。 impl<T> PrintInOption for T where Option<T>: Debug { // 出力されるのが`Some(self)`であるため、この関数の // ジェネリック境界として`Option<T>: Debug`を使用したい。 fn print_in_option(self) { println!("{:?}", Some(self)); } } fn main() { let vec = vec![1, 2, 3]; vec.print_in_option(); }
参照
ニュータイプイディオム
ニュータイプイディオムは正しい型の値が与えられていることをコンパイル時に保証することができます。
例えば、年齢を年単位で確認するold_enough
には「Years」という型の値を 与えなければならない ようにすることが可能です。
struct Years(i64); struct Days(i64); impl Years { pub fn to_days(&self) -> Days { Days(self.0 * 365) } } impl Days { /// 1年に満たない日付は切り捨て。 pub fn to_years(&self) -> Years { Years(self.0 / 365) } } fn is_adult(age: &Years) -> bool { age.0 >= 18 } fn main() { let age = Years(25); let age_days = age.to_days(); println!("Is an adult? {}", is_adult(&age)); println!("Is an adult? {}", is_adult(&age_days.to_years())); // println!("Is an adult? {}", is_adult(&age_days)); }
最後の print文 のコメントを外して、与えられた型が Years
でなければならないことを確認してください。
newtype
の元に使われている型のデータを取得するには、以下のようにタプルやデストラクト構文を用いることで取得できます。
struct Years(i64); fn main() { let years = Years(42); let years_as_primitive_1: i64 = years.0; // タプル let Years(years_as_primitive_2) = years; // デストラクト }
参照
関連要素
関連要素とは複数の型の要素に関係のある規則の総称です。トレイトの拡張機能であり、トレイトの中で新しい要素を定義することを可能にします。
そのように定義する要素の一つに 関連型 があります。これにより、ジェネリックなコンテナ型に対するトレイトを使用する際に、よりシンプルな書き方ができるようになります。
参照
関連要素が必要になる状況
コンテナ型に、その要素に対してジェネリックなトレイトを実装した場合、そのトレイトを使用する者は全てのジェネリック型を明記 しなくてはなりません 。
以下の例ではContains
トレイトはジェネリック型A
とB
の使用を許しています。その後、Container
型に対してContains
を実装していますが、その際後にfn difference()
が使用できるように、A
、B
はそれぞれi32
と明記されています。
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 { // コンテナ内の2つの要素が等しければTrueを返します。 fn contains(&self, number_1: &i32, number_2: &i32) -> bool { (&self.0 == number_1) && (&self.1 == number_2) } // ひとつ目の値を取得。 fn first(&self) -> i32 { self.0 } // 最後(2つめ)の値を取得。 fn last(&self) -> i32 { self.1 } } // `A`と`B`は`C`に保持されていることを考慮すると、`A`と`B`を // 2度も書くのは面倒。 fn difference<A, B, C>(container: &C) -> i32 where C: Contains<A, B> { container.last() - container.first() } fn main() { let number_1 = 3; let number_2 = 10; let container = Container(number_1, number_2); println!("Does container contain {} and {}: {}", &number_1, &number_2, container.contains(&number_1, &number_2)); println!("First number: {}", container.first()); println!("Last number: {}", container.last()); println!("The difference is: {}", difference(&container)); }
参照
関連型
関連型を使用すると、コンテナ型の中の要素をトレイトの中に 出力型 として書くことで、全体の可読性を上げることができます。トレイトを定義する際の構文は以下のようになります。
#![allow(unused)] fn main() { // `A`と`B`は`type`キーワードを用いてトレイト内で宣言されています。 // (注意:この文脈で使用する`type`は型エイリアスを宣言する際の`type`とは // 異なることに注意しましょう。) trait Contains { type A; type B; // これらの新しい型をジェネリックに使用するために、構文が // アップデートされています。 fn contains(&self, _: &Self::A, _: &Self::B) -> bool; } }
Contains
トレイトを使用する関数において、A
とB
を明示する必要がなくなっていることに注目しましょう。
// 関連型を使用しない場合
fn difference<A, B, C>(container: &C) -> i32 where
C: Contains<A, B> { ... }
// 使用する場合
fn difference<C: Contains>(container: &C) -> i32 { ... }
前セクションの例を関連型を使用して書きなおしてみましょう。
struct Container(i32, i32); // 2つの要素がコンテナ型の中にあることをチェックするトレイト // また、最初と最後の値を取得することもできます。 trait Contains { // メソッドが使用できるジェネリック型を定義。 type A; type B; fn contains(&self, _: &Self::A, _: &Self::B) -> bool; fn first(&self) -> i32; fn last(&self) -> i32; } impl Contains for Container { // `A`と`B`がどの型であるかを明示。入力型(訳注:つまり`Self`の型) // が`Container(i32, i32)`である場合、出力型は`i32`と`i32`となります。 type A = i32; type B = i32; // `&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 } // 最後(2つめ)の値を取得。 fn last(&self) -> i32 { self.1 } } fn difference<C: Contains>(container: &C) -> i32 { container.last() - container.first() } fn main() { let number_1 = 3; let number_2 = 10; let container = Container(number_1, number_2); println!("Does container contain {} and {}: {}", &number_1, &number_2, container.contains(&number_1, &number_2)); println!("First number: {}", container.first()); println!("Last number: {}", container.last()); println!("The difference is: {}", difference(&container)); }
幽霊型パラメータ
幽霊型とは実行時には存在しないけれども、コンパイル時に静的に型チェックされるような型のことです。
構造体などのデータ型は、ジェネリック型パラメータを一つ余分に持ち、それをマーカーとして使ったりコンパイル時の型検査に使ったりすることができます。このマーカーは実際の値を何も持たず、したがって実行時の挙動そのものにはいかなる影響ももたらしません。
以下の例では、そのようなマーカーとして幽霊型(std::marker::PhantomData)を用い、それぞれ異なった型の値を持つタプルを作成します。
use std::marker::PhantomData; // ジェネリックなタプル構造体。2つ目のパラメータは幽霊型。 #[derive(PartialEq)] // 比較演算子(`==`)での比較を可能にします。 struct PhantomTuple<A, B>(A, PhantomData<B>); // 同様に構造体を定義。 #[derive(PartialEq)] // 比較演算子(`==`)での比較を可能にします。 struct PhantomStruct<A, B> { first: A, phantom: PhantomData<B> } // 注意点: ジェネリック型Aに対してはメモリが割り当てられていますが、 // Bには割り当てられていないため、計算に使うことはできません。 fn main() { // ここで `f32` と `f64` は隠しパラメータです。 // <char, f32>と型宣言されたPhantomTupleを作成。 let _tuple1: PhantomTuple<char, f32> = PhantomTuple('Q', PhantomData); // <char, f64>と型宣言されたPhantomTupleを作成。 let _tuple2: PhantomTuple<char, f64> = PhantomTuple('Q', PhantomData); // <char, f32>の型が与えられた構造体を作成。 let _struct1: PhantomStruct<char, f32> = PhantomStruct { first: 'Q', phantom: PhantomData, }; // 同様に<char, f64>の構造体 let _struct2: PhantomStruct<char, f64> = PhantomStruct { first: 'Q', phantom: PhantomData, }; // コンパイルエラー!型が違うので比較することができません! // println!("_tuple1 == _tuple2 yields: {}", // _tuple1 == _tuple2); // コンパイルエラー! 型が違うので比較することができません! // println!("_struct1 == _struct2 yields: {}", // _struct1 == _struct2); }
参照
テストケース:単位を扱う
共通の単位同士を扱う際のチェックのために、Add
を幽霊型を用いた実装にすると便利な場合があります。その場合Add
トレイトは以下のようになります。
// このように定義しておくと、`Self + RHS = Output`であることが保証され、
// かつ、impl時にRHSの型が明示されていない場合、デフォルトでSelfと同じに
// なります。
pub trait Add<RHS = Self> {
type Output;
fn add(self, rhs: RHS) -> Self::Output;
}
// `Output`は`T<U>`でなくてはならないので`T<U> + T<U> = 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`の新しいインスタンスを返します。 // 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
), 列挙型, impl & self, 演算子のオーバーロード, ref, トレイト (X for Y
), タプル構造体.
スコープの規則
所有権、借用、ライフタイムといったRustに特有の概念において、変数のスコープは重要な役割を果たします。すなわち、スコープの存在によってコンパイラは借用は可能か否か、メモリ上の資源は解放可能か、変数はいつ作成され、いつ破棄されるか。といったことを知るのです。
RAII
Rustの変数は単にデータをスタック上に保持するだけのものではありません。例えばヒープメモリを確保するBox<T>
のように、変数はメモリ上の資源を 保有 する場合もあるのです。RustはRAII(Resource Acquisition Is Initialization)を強制するので、オブジェクトがスコープを抜けると、必ずデストラクタが呼び出されてそのオブジェクトが保持していた資源が解放されます。
この振る舞いは リソースリーク バグを防ぐのに役立ちます。手動でメモリを解放したり、メモリリークバグにわずらわされたりすることはなくなるのです!簡単な例で説明しましょう。
// raii.rs fn create_box() { // 整数をヒープ上に確保。 let _box1 = Box::new(3i32); // `_box1`はここで破棄され、メモリは解放されます。 } fn main() { // 整数をヒープ上に確保。 let _box2 = Box::new(5i32); // ネストしたスコープ { // 整数をヒープ上に確保。 let _box3 = Box::new(4i32); // `_box3`はここで破棄され、メモリは解放されます。 } // お遊びで大量のボックスを作ります。 // もちろん手動で開放する必要はないよ! for _ in 0u32..1_000 { create_box(); } // `_box2`はここで破棄され、メモリは解放されます。 }
valgrind
を用いて、メモリエラーが起きていないか2重チェックすることももちろん可能です。
$ 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
トレイトは型定義のたびに必ず実装する必要があるわけではなく、デストラクタに独自のロジックが必要な場合にのみ実装します。
下のコードを実行して、Drop
トレイトの動作を確認してみましょう。main
関数内の変数がスコープを抜けるときにカスタムデストラクタが呼び出されるはずです。
struct ToDrop; impl Drop for ToDrop { fn drop(&mut self) { println!("ToDrop is being dropped"); } } fn main() { let x = ToDrop; println!("Made a ToDrop!"); }
参照
所有権とムーブ
変数には自身の保持する資源を開放する責任があるため、資源は一度に一つの所有者 しか持つことができません。これはまた、資源を2度以上開放することができないということでもあります。ここで、全ての変数が資源を所有するわけではないということに注意しましょう。(e.g. 参照)
変数を代入する(let x = y
)際や、関数に引数を値渡しする(foo(x)
)際は、資源の 所有権 が移動します。Rustっぽく言うと、「 ムーブ 」です。
資源を移動すると、それまでの所有者(訳注:変数などのこと)を使用することはできなくなります。これによりダングリングポインタの発生を防げます。
// この関数はヒープメモリ上の資源の所有権を取ります。 fn destroy_box(c: Box<i32>) { println!("Destroying a box that contains {}", c); // `c`は破棄されメモリは開放されます。 } fn main() { // _スタック_上に置かれた整数 let x = 5u32; // `x`を`y`に *コピー* します。元の値が移動するわけではありません。 let y = x; // 両方の値はそれぞれ独立に使うことができます。 println!("x is {}, and y is {}", x, y); // `a`は_ヒープ_上の整数へのポインタ。 let a = Box::new(5i32); println!("a contains: {}", a); // `a`を`b`に *ムーブ* します。 let b = a; // すなわち、`a`の指すメモリ上の番地が`b`にコピーされるため // いずれもヒープ上の同じ値を指すポインタとなります。 // しかし所有権は`b`にあります。 // エラー!`a`は所有権を持たないため、ヒープ上のデータにアクセスできません。 //println!("a contains: {}", a); // TODO ^ 試しにここをアンコメントしてみましょう。 // この関数はヒープメモリ上の所有権を`b`から取ります。 destroy_box(b); // この時点でヒープメモリ上の資源は開放されているので、次の操作は // 解放済みメモリをデリファレンスすることになります。 // しかしそれはコンパイラが許しません。 // エラー!上述の理由より //println!("b contains: {}", b); // TODO ^ 試しにここをアンコメントしてみましょう。 }
ミュータビリティ
データのミュータビリティは所有権を移譲した際に変更できます。
fn main() { let immutable_box = Box::new(5u32); println!("immutable_box contains {}", immutable_box); // ミュータビリティエラー //*immutable_box = 4; // boxを *ムーブ* する、同時に所有権とミュータビリティを変更します。 let mut mutable_box = immutable_box; println!("mutable_box contains {}", mutable_box); // boxの内容を変更。 *mutable_box = 4; println!("mutable_box now contains {}", mutable_box); }
部分的ムーブ
1つの変数の デストラクト の中で、ムーブ
と 参照
の両方のパターン束縛を同時に使用することができます。両方を使用すると、変数の一部がムーブされ、他の部分が参照として残るという変数の部分的ムーブが発生した状態になります。変数の部分的ムーブが発生すると親変数はその後使用できませんが、参照されているだけの部分(ムーブされていない部分)は使用することができます。
fn main() { #[derive(Debug)] struct Person { name: String, age: Box<u8>, } 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
は必須ではないのですが、実際にはヒープ上に保持されているためref
は必須です。
参照
借用
実際には、データの所有権を完全に受け渡すことなく一時的にアクセスしたいという場合がほとんどです。そのために、Rustでは 借用 という仕組みを用います。値そのもの(T
)を受け渡すのではなく、その参照(&T
)を渡すのです。
コンパイラは借用チェッカを用いて参照が 常に 有効なオブジェクトへの参照であることを、コンパイル時に保証します。つまり、あるオブジェクトへの参照が存在しているならば、そのオブジェクトを破壊することはできないということです。
// この関数はボックスの所有権を奪い、破棄します。 fn eat_box_i32(boxed_i32: Box<i32>) { println!("Destroying box that contains {}", boxed_i32); } // この関数はi32を借用します。 fn borrow_i32(borrowed_i32: &i32) { println!("This int is: {}", borrowed_i32); } fn main() { // Create a boxed i32 in the heap, and a i32 on the stack // Remember: numbers can have arbitrary underscores added for readability // 5_i32 is the same as 5i32 let boxed_i32 = Box::new(5_i32); let stacked_i32 = 6_i32; // Boxの中身を借用。所有権を奪うわけではないため、 // 直後にもう一度借用できます。 borrow_i32(&boxed_i32); borrow_i32(&stacked_i32); { // ボックス内の要素に対する参照を取得。 let _ref_to_i32: &i32 = &boxed_i32; // エラー! // ボックス内の要素が借用されているため、`boxed_i32`を破棄する // ことはできません。 eat_box_i32(boxed_i32); // FIXME ^ この行をコメントアウトしましょう // Attempt to borrow `_ref_to_i32` after inner value is destroyed borrow_i32(_ref_to_i32); // ここで`_ref_to_i32`はスコープを抜け、借用もなくなります。 } // `boxed_i32` can now give up ownership to `eat_box_i32` and be destroyed eat_box_i32(boxed_i32); }
ミュータビリティ
ミュータブルなデータは&mut T
でミュータブルに(変更可能な形で)借用することができます。これは ミュータブルな参照 と呼ばれ、読み込み・書き込みの権限を借用者に与えます。対照的に&T
はデータをイミュータブルな参照を介して借用し、借用した側はデータを読み込みはできますが書き込みはできません。
#[allow(dead_code)] #[derive(Clone, Copy)] struct Book { // `&'static str`はread-onlyメモリ上の文字列への参照。 author: &'static str, title: &'static str, year: u32, } // この関数はBook型への参照を取ります。 fn borrow_book(book: &Book) { println!("I immutably borrowed {} - {} edition", book.title, book.year); } // この関数はミュータブルなBook型へのミュータブルな参照を取り、 // `year`を2014へ変更します。 fn new_edition(book: &mut Book) { book.year = 2014; println!("I mutably borrowed {} - {} edition", book.title, book.year); } fn main() { // `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 ^ この行をコメントアウトしましょう }
参照
エイリアス
データは一度にいくつでもイミュータブルに借用することができますが、その間オリジナルのデータをミュータブルに借用することはできません。一方でミュータブルな借用は一度に 一つ しか借用することができません。オリジナルのデータをもう一度借用できるのはミュータブルな参照が最後に使われた場所より あとで なければいけません。
struct Point { x: i32, y: i32, z: i32 } fn main() { let mut point = Point { x: 0, y: 0, z: 0 }; let borrowed_point = &point; let another_borrow = &point; // データは元々の持ち主と参照の両方からアクセスすることができます。 println!("Point has coordinates: ({}, {}, {})", borrowed_point.x, another_borrow.y, point.z); // エラー!pointはすでにイミュータブルに借用されているため、 // ミュータブルに借用することができません。 // let mutable_borrow = &mut point; // TODO ^ この行をアンコメントしてみましょう。 // 借用された値はここで再び利用されます。 println!("Point has coordinates: ({}, {}, {})", borrowed_point.x, another_borrow.y, point.z); // イミュータブルな参照がこれ以降のコードで利用されていないため、 // ミュータブルな参照として再借用できます。 let mutable_borrow = &mut point; // ミュータブルな参照を介してデータを変更します。 mutable_borrow.x = 5; mutable_borrow.y = 2; mutable_borrow.z = 1; // エラー!`point`はすでにミュータブルに借用されているため、 // イミュータブルに借用することはできません。 // let y = &point.y; // TODO ^ この行をアンコメントしてみましょう。 // エラー!`println!`はイミュータブルな参照を取るため、printできません。 // println!("Point Z coordinate is {}", point.z); // TODO ^ この行をアンコメントしてみましょう。 // OK!ミュータブルな参照は`println!`にイミュータブルな参照として渡せます。 println!("Point has coordinates: ({}, {}, {})", mutable_borrow.x, mutable_borrow.y, mutable_borrow.z); // ミュータブルな参照がこれ以降のコードで未使用なので、再借用できます。 let new_borrowed_point = &point; println!("Point now has coordinates: ({}, {}, {})", new_borrowed_point.x, new_borrowed_point.y, new_borrowed_point.z); }
refパターン
let
を介してデストラクトやパターンマッチングを行う場合、ref
キーワードを用いて構造体・タプルのフィールドへの参照を取得することができます。以下の例ではこれが有用になる例を幾つかお見せします。
#[derive(Clone, Copy)] struct Point { x: i32, y: i32 } fn main() { let c = 'Q'; // 左辺に`ref`をつけることによる借用と、右辺に`&`をつけることによる借用は等価。 let ref ref_c1 = c; let ref_c2 = &c; println!("ref_c1 equals ref_c2: {}", *ref_c1 == *ref_c2); let point = Point { x: 0, y: 0 }; // `ref`は構造体をデストラクトする際にも有用。 let _copy_of_x = { // `ref_to_x`は`point`の`x`フィールドへの参照。 let Point { x: ref ref_to_x, y: _ } = point; // `point`の`x`フィールドへのコピーを返します。 *ref_to_x }; // `point`へのミュータブルなコピー let mut mutable_point = point; { // `ref`は`mut`とともに使い、ミュータブルな参照を取ることもできます。 let Point { x: _, y: ref mut mut_ref_to_y } = mutable_point; // `mutable_point`の`y`というミュータブルなフィールドの値を変更します。 *mut_ref_to_y = 1; } println!("point is ({}, {})", point.x, point.y); println!("mutable_point is ({}, {})", mutable_point.x, mutable_point.y); // ポインタを含むミュータブルなタプル let mut mutable_tuple = (Box::new(5u32), 3u32); { // `mutable_tuple`をデストラクトして、`last`の値を変更。 let (_, ref mut last) = mutable_tuple; *last = 2u32; } println!("tuple is {:?}", mutable_tuple); }
ライフタイム
ライフタイム はコンパイラ(借用チェッカーと呼ばれる場合もあります)が、全ての借用に問題がないことを確認するために使用する仕組みです。正確にいうと、変数のライフタイムは作成時に開始し、破棄された時に終了します。ライフタイムとスコープは同時に語られることが多いですが、同じものではありません。
例として&
を用いて変数を借用する場合をあげましょう。借用のライフタイムは宣言時に決定し、そこから貸し手が破棄されるまで続きます。しかしながら、借用のスコープは参照が使われる際に決定します。
以下の例からこのセクションの残りでは、ライフタイムとスコープの関係、そしてそれらがいかに異なるものであるかを見ていきます。
// 以下では、変数の作成から破棄までのライフタイムを線で示しています。 // `i`は最長のライフタイムを持ち、そのスコープは`borrow1`および`borrow2` // のスコープを完全に包含します。 // `borrow1`と`borrow2`の存続期間は一切重なりません。 fn main() { let i = 3; // `i`のライフタイム開始 ───────────────────┐ // │ { // │ let borrow1 = &i; // `borrow1`のライフタイム開始──┐│ // ││ println!("borrow1: {}", borrow1); // ││ } // `borrow1` 終了 ──────────────────────────────────┘│ // │ // │ { // │ let borrow2 = &i; // `borrow2`のライフタイム開始──┐│ // ││ println!("borrow2: {}", borrow2); // ││ } // `borrow2` 終了 ──────────────────────────────────┘│ // │ } // ライフタイム終了 ───────────────────────────────────┘
ここで、一切の名前や型がライフタイムに代入されていないことに注意しましょう。これにより、ライフタイムの使われ方がこれから見ていくようなやり方に制限されます。
明示的アノテーション
借用チェッカーは参照がどれだけの間有効かを決定するために、明示的なアノテーションを使用します。ライフタイムが省略1されなかった場合、Rustは参照のライフタイムがどのようなものであるか、明示的なアノテーションを必要とします。
foo<'a>
// `foo`は`'a`というライフタイムパラメータを持ちます。
クロージャと同様、ライフタイムの使用はジェネリクスを必要とします。もう少し詳しく言うと、この書き方は「foo
のライフタイムは'a
のそれを超えることはない。」ということを示しており、型を明示した場合'a
は&'a T
となるということです。
ライフタイムが複数ある場合も、同じような構文になります。
foo<'a, 'b>
// `foo`は`'a`と`'b`というライフタイムパラメータを持ちます。
この場合は、foo
のライフタイムは'a
、'b
の いずれよりも 長くなってはなりません。
以下はライフタイムを明示的に書く場合の例です。
// `print_refs`は`i32`への参照を2つとり、それぞれ`'a`と`'b`という // ライフタイムを持ちます。これらのライフタイムは最短でも`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; // `&_x`のライフタイムは`y`のそれよりも短いため、関数内で`'a`を使用して // 変数のライフタイムを指定しようとすると失敗します。つまり、短いライフタイム // を持つ参照をより長いものに強制的に代入することはできません。 } fn main() { // 下で借用するための変数を作成。 let (four, nine) = (4, 9); // 2つの変数の借用(`&`)が関数に渡されます。 print_refs(&four, &nine); // 借用された変数の寿命は、借り手のそれよりも長くなくてはなりません。 // つまり、`four`、`nine`のライフタイムは`print_refs`のそれよりも // 長くなくてはなりません。 failed_borrow(); // `failed_borrow`は関数のライフタイムよりも`'a`を長くさせるような // 参照を持ちませんが、それでも`'a`のほうが長くなります。なぜならそのような // 場合`'a`はデフォルトで`'static`になるからです。 }
省略 はライフタイムが暗黙のうちに(プログラマから見えない形で)アノテートされることを指します。
参照
関数
省略をしない場合、ライフタイムのシグネチャ(e.g. <'a>
)を持つ関数にはいくつかの制限があります。
- 全ての参照においてライフタイムを明示しなくてはなりません。
- 返り値となる参照はすべて引数と同じライフタイムか、
static
ライフタイムを持たなくてはなりません
加えて、引数のない関数から参照を返すことは、それが結果的に無効なデータへの参照になるならば、禁止されています。ライフタイムを持つ関数の例をいくつか示します。
// 引数として`'a`のライフタイムで参照を一つ取ります。最低でもこの関数 // と同じだけの長さでなくてはなりません。 fn print_one<'a>(x: &'a i32) { println!("`print_one`: x is {}", x); } // ミュータブルな参照でも同様。 fn add_one<'a>(x: &'a mut i32) { *x += 1; } // 異なるライフタイムを持つ複数の引数がある場合。 // ここでは1種類のライフタイムでも問題ありませんが、より複雑なケースでは // 異なるライフタイムが必要になる場合があります。 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 } // `'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`型。 // 参照は`Borrowed`自体よりも長生きでなくてはなりません。 #[derive(Debug)] struct Borrowed<'a>(&'a i32); // 同様に、ここでも参照は構造体よりも長生きでなくてはなりません。 #[derive(Debug)] struct NamedBorrowed<'a> { x: &'a i32, y: &'a i32, } // `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 is borrowed in {:?}", single); println!("x and y are borrowed in {:?}", double); println!("x is borrowed in {:?}", reference); println!("y is *not* borrowed in {:?}", number); }
参照
トレイト
トレイトのメソッドにおけるライフタイムのアノテーションは、基本的には関数に似ています。impl
にもライフタイムのアノテーションがあることに注意してください。
// ライフタイムのアノテーションつき構造体 #[derive(Debug)] struct Borrowed<'a> { x: &'a i32, } // ライフタイムのアノテーションつきimpl impl<'a> Default for Borrowed<'a> { fn default() -> Self { Self { x: &10, } } } fn main() { let b: Borrowed = Default::default(); println!("b is {:?}", b); }
参照
境界
ジェネリック型に境界を与え、特定のトレイトを実装していることを保証できるのと同様、ライフタイム(それ自身ジェネリック型)にも境界を与えることができます。:
は、ここでは多少異なる意味を持ちますが+
は同じです。以下の構文の意味をチェックしてください。
T: 'a
:T
内の 全ての 参照は'a
よりも長生きでなくてはなりません。T: Trait + 'a
:上に加えてT
はTrait
という名のトレイトを実装してなくてはなりません。
上記の構文を実際に動く例で見ていきましょう。where
キーワードの後に注目してください。
use std::fmt::Debug; // ライフタイムを紐付けるトレイト #[derive(Debug)] struct Ref<'a, T: 'a>(&'a T); // `Ref` contains a reference to a generic type `T` that has // some lifetime `'a` unknown by `Ref`. `T` is bounded such that any // *references* in `T` must outlive `'a`. Additionally, the lifetime // of `Ref` may not exceed `'a`. // `Debug`トレイトを利用して出力を行うジェネリック関数 fn print<T>(t: T) where T: Debug { println!("`print`: t is {:?}", t); } // `Debug`を実装している`T`への参照を取りません。`T`への *参照* は // 必ず`'a`よりも長生きでなくてはなりません。さらに、`'a`は // 関数自体よりも長生きでなくてはなりません。 fn print_ref<'a, T>(t: &'a T) where T: Debug + 'a { println!("`print_ref`: t is {:?}", t); } fn main() { let x = 7; let ref_x = Ref(&x); print_ref(&ref_x); print(ref_x); }
参照
強制
長いライフタイムは、短いものに圧縮することで、そのままでは動作しないスコープの中でも使用できるようになります。これは、Rustコンパイラが推論の結果として圧縮する場合と、複数のライフタイムを比較して圧縮する場合があります。
// ここではRustはライフタイムを出来る限り短く見積もり、 // 2つの参照をそのライフタイムに押し込めます。 fn multiply<'a>(first: &'a i32, second: &'a i32) -> i32 { first * second } // `<'a: 'b, 'b>`は「ライフタイム`'a`は最低でも`'b`と同じ長さ」と読めます。 // ここでは、`&'a i32`をとり、`&'b i32`に圧縮して返します。 fn choose_first<'a: 'b, 'b>(first: &'a i32, _: &'b i32) -> &'b i32 { first } fn main() { let first = 2; // 長いライフタイム { let second = 3; // 短いライフタイム println!("The product is {}", multiply(&first, &second)); println!("{} is the first", choose_first(&first, &second)); }; }
スタティックライフタイム
Rustにはいくつかの予約されたライフタイム名があります。その1つがstatic
で、2つの状況で使用することがあります。
// A reference with 'static lifetime: let s: &'static str = "hello world"; // 'static as part of a trait bound: fn generic<T>(x: T) where T: 'static {}
2つの状況におけるstatic
は微妙に異なる意味を持っており、Rustを学ぶときの混乱の元になっています。いくつかの例とともにそれぞれの使い方を見てみましょう。
参照のライフタイム
参照のライフタイムが'static
であることは、参照が指し示す値がプログラムの実行中に渡って生き続けることを示します。また、より短いライフタイムに圧縮することも可能です。
'static
ライフタイムを持つ変数を作るには下記の2つ方法があります。どちらの場合も、値は読み取り専用のメモリ領域に格納されます。
static
宣言とともに定数を作成します。- 文字列リテラルで
&'static str
型を持つ変数を作成します。
では、それぞれの方法の例を見ていきましょう。
// `'static`ライフタイムを持つ定数を作成。 static NUM: i32 = 18; // `NUM`への参照を返します。ライフタイムは`'static`から引数の // ライフタイムへと圧縮されています。 fn coerce_static<'a>(_: &'a i32) -> &'a i32 { &NUM } fn main() { { // 文字列リテラルを用いて変数を作成し、出力します。 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); }
Since 'static
references only need to be valid for the remainder of a program's life, they can be created while the program is executed. Just to demonstrate, the below example uses Box::leak
to dynamically create 'static
references. In that case it definitely doesn't live for the entire duration, but only for the leaking point onward.
extern crate rand; use rand::Fill; fn random_vec() -> &'static [usize; 100] { let mut rng = rand::thread_rng(); let mut boxed = Box::new([0; 100]); boxed.try_fill(&mut rng).unwrap(); Box::leak(boxed) } fn main() { let first: &'static [usize; 100] = random_vec(); let second: &'static [usize; 100] = random_vec(); assert_ne!(first, second) }
トレイト境界
トレイト境界としての'static
は型が非静的な参照を含まないことを意味します。言い換えると、レシーバはその型をいくらでも長く保持することができ、意図的にドロップするまでは決して無効になることはないということです。
次のポイントを押さえておきましょう。所有権のある値が'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`のライフタイムはコンパイラによって自動的に付与されるため // 以下の2つは同一のライフタイムシグネチャを持ちます。 fn elided_input(x: &i32) { println!("`elided_input`: {}", x); } fn annotated_input<'a>(x: &'a i32) { println!("`annotated_input`: {}", x); } // 同様に、以下の2つの関数も全く同じライフタイムシグネチャを持ちます。 fn elided_pass(x: &i32) -> &i32 { x } fn annotated_pass<'a>(x: &'a i32) -> &'a i32 { x } fn main() { let x = 3; elided_input(&x); annotated_input(&x); println!("`elided_pass`: {}", elided_pass(&x)); println!("`annotated_pass`: {}", annotated_pass(&x)); }
参照
トレイト
トレイトとは任意の型となりうるSelf
に対して定義されたメソッドの集合のことです。同じトレイト内で宣言されたメソッド同士はお互いにアクセスすることができます。
トレイトはあらゆるデータ型に実装することができます。以下の例ではまずAnimal
というメソッドの集合を定義し、その後Animal
トレイトをSheep
というデータ型に対して実装します。これによりAnimal
のメソッドをSheep
が使用することが可能になります。
struct Sheep { naked: bool, name: &'static str } trait Animal { // 関連関数のシグネチャ // `Self` はこのトレイトを実装している型になります。 fn new(name: &'static str) -> Self; // メソッドのシグネチャ // これらの関数は文字列を返します。 fn name(&self) -> &'static str; fn noise(&self) -> &'static str; // メソッドのデフォルトの挙動を定義することもできます。 fn talk(&self) { println!("{} says {}", self.name(), self.noise()); } } impl Sheep { fn is_naked(&self) -> bool { self.naked } fn shear(&mut self) { if self.is_naked() { // メソッドをある型に実装する際に、その型のトレイトメソッドを // 使用することができます。 println!("{} is already naked...", self.name()); } else { println!("{} gets a haircut!", self.name); self.naked = true; } } } // `Animal`というトレイトを`Sheep`に実装します。 impl Animal for Sheep { // `Self`は実装対象の型:ここでは`Sheep` fn new(name: &'static str) -> Sheep { Sheep { name: name, naked: false } } fn name(&self) -> &'static str { self.name } fn noise(&self) -> &'static str { if self.is_naked() { "baaaaah?" } else { "baaaaah!" } } // デフォルトのトレイトメソッドはオーバーライドすることができます。 fn talk(&self) { // 例えば、静かに熟考させてみます。 println!("{} pauses briefly... {}", self.name, self.noise()); } } fn main() { // この場合、型アノテーションが必須。 let mut dolly: Sheep = Animal::new("Dolly"); // TODO ^ ここの型アノテーションを消してみましょう。 dolly.talk(); dolly.shear(); dolly.talk(); }
導出(Derive)
コンパイラには、#[derive]
アトリビュートを用いることで型に対して特定のトレイトの標準的な実装を提供する機能があります。より複雑なことを行わせたい場合には、同名のトレイトを手動で実装することも可能です。
以下は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); // エラー:`Debug`トレイトを実装していないため`Seconds`は出力できません。 //println!("One second looks like: {:?}", _one_second); // TODO ^ この行をアンコメントしてみましょう。 // エラー`PartialEq`トレイトを実装していないため`Seconds`は比較できません。 //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は可能な限り明示的であろうとします。なので、もしあなたの関数がヒープ上のトレイトへのポインタを返す場合、例えばBox<dyn Animal>
のように、リターン型にdyn
キーワードをつける必要があります。
struct Sheep {} struct Cow {} trait Animal { // インスタンスメソッドのシグネチャ fn noise(&self) -> &'static str; } // `Animal`というトレイトを`Sheep`に実装します。 impl Animal for Sheep { fn noise(&self) -> &'static str { "baaaaah!" } } // `Cow`に`Animal`トレイトを実装します。 impl Animal for Cow { fn noise(&self) -> &'static str { "moooooo!" } } // Animalを実装した何らかの構造体を返します。 // ただし、コンパイル時にはどの実装か分かりません。 fn random_animal(random_number: f64) -> Box<dyn Animal> { if random_number < 0.5 { Box::new(Sheep {}) } else { Box::new(Cow {}) } } fn main() { let random_number = 0.234; let animal = random_animal(random_number); println!("You've randomly chosen an animal, and it says {}", animal.noise()); }
演算子のオーバーロード
Rustでは、多くの演算子はトレイトによってオーバーロードすることができます。つまり、一部の演算子は引数となる値の型に応じて異なる役割を果たすことができるということです。これが可能なのは、演算子が実際にはメソッド呼び出しの糖衣構文にすぎないからです。例えばa + b
における+
演算子はadd
メソッドを(a.add(b)
の形で)呼び出します。このadd
メソッドはAdd
トレイトの一部です。それ故、+
はAdd
トレイトを実装している全ての型に対して有効なのです。
Add
などの、演算子をオーバーロードするトレイトの一覧はcore::ops
にあります。
use std::ops; struct Foo; struct Bar; #[derive(Debug)] struct FooBar; #[derive(Debug)] struct BarFoo; // `std::ops::Add`トレイトは`+`の振る舞いを規定するために使用されます。 // ここでは`Foo`に対して`Add<Bar>`を実装します。これは加算時の右辺が`Bar`型 // の時に呼び出されるトレイトです。 // つまり以下は`Foo + Bar = FooBar`という振る舞いをもたらします。 impl ops::Add<Bar> for Foo { type Output = FooBar; fn add(self, _rhs: Bar) -> FooBar { println!("> Foo.add(Bar) was called"); FooBar } } // 型を反転することで、非可換の加算を実装できます。ここでは`Bar`に対して // `Add<Foo>`を実装します。 // これは加算時の右辺が`Foo`型の時に呼び出されるメソッドです。 // つまり以下は`Bar + Foo = BarFoo`という結果をもたらします。 impl ops::Add<Foo> for Bar { type Output = BarFoo; fn add(self, _rhs: Foo) -> BarFoo { println!("> Bar.add(Foo) was called"); BarFoo } } fn main() { println!("Foo + Bar = {:?}", Foo + Bar); println!("Bar + Foo = {:?}", Bar + Foo); }
参照
ドロップ
Drop
トレイトにはメソッドが一つだけしかありません。drop
です。これは、オブジェクトがスコープから抜けた時に自動で呼ばれます。Drop
トレイトの主な使用目的は、インスタンスが所有する資源を開放することです。
Drop
トレイトを実装している型の例としてはBox
、Vec
、String
、File
、Process
等があげられます。Drop
トレイトは任意の型に対して手動で実装することができます。
以下の例ではdrop
メソッドにコンソールへの出力を追加することで、drop
が呼ばれたタイミングが分かるようにしています。
struct Droppable { name: &'static str, } // このちょっとした実装で、`drop`にコンソール出力機能がつきます。 impl Drop for Droppable { fn drop(&mut self) { println!("> Dropping {}", self.name); } } fn main() { let _a = Droppable { name: "a" }; // block A { let _b = Droppable { name: "b" }; // block B { let _c = Droppable { name: "c" }; let _d = Droppable { name: "d" }; println!("Exiting block B"); } println!("Just exited block B"); println!("Exiting block A"); } println!("Just exited block A"); // `drop`関数を用いて変数を手動で開放することもできます。 drop(_a); // TODO ^ この行をコメントアウトしてみましょう。 println!("end of the main function"); // `_a`はここで`drop`されることは *ありません* 。なぜならば、上ですでに // (手動で)`drop`されているためです。 }
イテレータ
Iterator
トレイトは、例えば配列のような、要素の集合に対してイテレータを実装するためのトレイトです。
このトレイトはnext
の要素に相当するものを決定するためのメソッドのみを要求します。このメソッドはimpl
ブロック内で手動で実装するか、あるいは(配列やrangeのように)自動で定義されます。
サッとイテレータを使いたい時は、for
文で集合からイテレータを作成することが良くあります。これは.into_iter()
メソッドを呼び出しています。
struct Fibonacci { curr: u32, next: u32, } // Implement `Iterator` for `Fibonacci`. // The `Iterator` trait only requires a method to be defined for the `next` element, // and an `associated type` to declare the return type of the iterator. impl Iterator for Fibonacci { // We can refer to this type using Self::Item type Item = u32; // ここではイテレーションの流れを`.curr`と`.next`を使用して定義しています。 // 返り値の型は`Option<T>`で、これは: // * `Iterator`が終了した時は`None`を返します。 // * そうでなければ`Some`でラップされた値を返します。 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`は`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)`メソッドは`Iterator`の先頭から`n`番目までの要素をとばします。 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`メソッドは配列やスライスからイテレータを作成します。 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
はジェネリックなので、BufReadを実装する任意の型を取ることができます。例えば、BufReader<File>
や[u8]
です。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
を引数の型として利用するということは、どのような形式の関数であるか明示できないので、注意してください。例えば、parse_csv_document::<std::io::Empty>(std::io::empty())
は2番目の例では動作しません。
リターン型
あなたの関数が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
トレイトはまさにこのためにあります。普通はClone
トレイトで定義されている.clone()
を用います。
// いかなる資源も持たない構造体 #[derive(Debug, Clone, Copy)] struct Unit; // `Clone`トレイトを実装する型の変数を資源として持つタプル #[derive(Clone, Debug)] struct Pair(Box<i32>, Box<i32>); fn main() { // `Unit`のインスタンスを作成。 let unit = Unit; // `Unit`をコピー、移動させる資源は存在しません。 let copied_unit = unit; // いずれの`Unit`も独立に使用できます。 println!("original: {:?}", unit); println!("copy: {:?}", copied_unit); // `Pair`のインスタンスを作成。 let pair = Pair(Box::new(1), Box::new(2)); println!("original: {:?}", pair); // `pair`を`moved_pair`に移動、資源は移動します。 let moved_pair = pair; println!("moved: {:?}", moved_pair); // エラー!`pair`は資源を失っています。 //println!("original: {:?}", pair); // TODO ^ この行をアンコメントしてみましょう。 // `moved_pair`を`cloned_pair`にクローンします。(資源もクローンされます。) let cloned_pair = moved_pair.clone(); // std::mem::dropを用いて元のpairをドロップします。 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も実装する必要があります。 trait Student: Person { fn university(&self) -> String; } trait Programmer { fn fav_language(&self) -> String; } // CompSciStudent(コンピュータサイエンスの学生)はProgrammerとStudent両方のサブトレイトです。 // CompSciStudentを実装するには、両方のスーパートレイトを実装する必要があります。 trait CompSciStudent: Programmer + Student { fn git_username(&self) -> String; } fn comp_sci_student_greeting(student: &dyn CompSciStudent) -> String { format!( "My name is {} and I attend {}. My favorite language is {}. My Git username is {}", student.name(), student.university(), student.fav_language(), student.git_username() ) } fn main() {}
参照
The Rust Programming Language chapter on supertraits
トレイトの曖昧性解決
A type can implement many different traits. What if two traits both require the same name for a function? For example, many traits might have a method named get()
. They might even have different return types!
Good news: because each trait implementation gets its own impl
block, it's clear which trait's get
method you're implementing.
What about when it comes time to call those methods? To disambiguate between them, we have to use Fully Qualified Syntax.
trait UsernameWidget { // Get the selected username out of this widget fn get(&self) -> String; } trait AgeWidget { // Get the selected age out of this widget fn get(&self) -> u8; } // A form with both a UsernameWidget and an 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, }; // If you uncomment this line, you'll get an error saying // "multiple `get` found". Because, after all, there are multiple methods // named `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 chapter on Fully Qualified syntax
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の引数は$
が頭につきます。型は 識別子 でアノテーションされます。
macro_rules! create_function { // このマクロは`ident`識別子に対応する値を引数として取り // `$func_name`という名の関数を作成します。 // `ident`識別子は関数・変数の名前用の識別子です。 ($func_name:ident) => { fn $func_name() { // `stringify!`というマクロは`ident`を文字列に変えます。 println!("You called {:?}()", stringify!($func_name)); } }; } // 上のマクロを利用して`foo`、`bar`という名の関数を作成します。 create_function!(foo); create_function!(bar); macro_rules! print_result { // このマクロは`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 }); }
使用できる識別子には以下のようなものがあります。
block
expr
式に使用。ident
関数、変数の名前に使用。item
literal
はリテラル定数。pat
(パターン)path
stmt
(宣言)tt
(トークンツリー)ty
(型)vis
(可視性修飾子)(訳注:pub (crate)
とか)
完全なリストを見るには、Rustリファレンスを読んでください。
オーバーロード
マクロは異なる引数の組み合わせを取るようにオーバーロードすることができるため、macro_rules!
はマッチと似たような使い方をすることができます。
// `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),+) => ( // `find_min!`を残りの`$y`に対して再帰的に適用。 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 (Don't Repeat Yourself)
マクロは関数やテストなどにおいて、共通の部分を抽出することで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
Domain Specific Languages (ドメイン特化言語、DSLs)
ここで言う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"); }
The first call to drink
works. The second panics and thus the third is never called.
abort
and unwind
The previous section illustrates the error handling mechanism panic
. Different code paths can be conditionally compiled based on the panic setting. The current values available are unwind
and abort
.
Building on the prior lemonade example, we explicitly use the panic strategy to exercise different lines of code.
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"); }
Here is another example focusing on rewriting drink()
and explicitly use the unwind
keyword.
#[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"); }
The panic strategy can be set from the command line by using abort
or unwind
.
rustc lemonade.rs -C panic=abort
Option
とunwrap
以前の例では、甘いレモネードを飲んだ際にpanic
を呼び出すことによって、自由にプログラムの実行を失敗させられることが分かりました。では、何らかの飲み物を期待しているにもかかわらず、何も受け取らなかったらどうなるでしょう?これは悲惨なケースになるので、エラーハンドリングする必要があります!
このケースに対して、レモネードと同じように、空文字列(""
)と比較することもできますが、せっかくRustを使っているので、その代わりにコンパイラに飲み物がないケースを指摘させてみましょう。
std
ライブラリの中の、Option<T>
と呼ばれるenum
は、任意の型T
である変数の値が存在しない可能性がある場合に用いられます。値の状態によって、下記2つのパターンのうちの1つとして扱われます。
Some(T)
:型T
の値がある場合None
:値が存在しない場合
これらはmatch
を用いて明示的に扱うこともできますし、unwrap
で暗黙に処理することもできます。後者はSome
の中の値を返すかpanic
するかのどちらかです。
expectメソッドを用いて、panic
を手動でカスタマイズできることに触れておきましょう。これは(unwrap
をそのまま用いた場合よりも)内容が理解しやすいエラーメッセージを出力するのに役立ちます。次の例では、結果をより明示的に、可能ならいつでもpanic
できるように扱っていきます。
// 大人は経験豊富なので、大体どんな飲み物にも対処できます。 // あらゆる飲み物は`match`を用いて手動で処理されます。 fn give_adult(drink: Option<&str>) { // Specify a course of action for each case. match drink { Some("lemonade") => println!("Yuck! Too sugary."), Some(inner) => println!("{}? How nice.", inner), None => println!("No drink? Oh well."), } } // 他の人たちは甘い飲み物を飲む前に`panic`します。 // 全ての飲み物は`unwrap`を使って暗黙的に処理されます。 fn drink(drink: Option<&str>) { // `unwrap`を使用すると値が`None`だった際に`panic`を返します。 let inside = drink.unwrap(); if inside == "lemonade" { panic!("AAAaaaaa!!!!"); } println!("I love {}s!!!!!", 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
文を使うこともできますが、?
を使う方が簡単になることが多いでしょう。Option
のx
があるとすると、x?
を評価した値は、x
がSome
の場合はx
に格納された値となり、そうでなければ実行中の関数を終了させ、None
を返します。
fn next_birthday(current_age: Option<u8>) -> Option<String> { // If `current_age` is `None`, this returns `None`. // If `current_age` is `Some`, the inner `u8` value + 1 // gets assigned to `next_age` let next_age: u8 = current_age? + 1; Some(format!("Next year I will be {}", next_age)) }
多くの?
を共に使うことで、リーダブルなコードを書くことができます。
struct Person { job: Option<Job>, } #[derive(Clone, Copy)] struct Job { phone_number: Option<PhoneNumber>, } #[derive(Clone, Copy)] struct PhoneNumber { area_code: Option<u8>, number: u32, } impl Person { // その人の市外局番が存在する場合、取得します。 fn work_phone_area_code(&self) -> Option<u8> { // `?`がなければ、多くのネストされた`match`文を必要とするため、 // より長いコードとなります。 // 実際に書いて、どちらの方が簡単か確かめてみましょう。 self.job?.phone_number?.area_code } } fn main() { let p = Person { job: Some(Job { phone_number: Some(PhoneNumber { area_code: Some(61), number: 439222222, }), }), }; assert_eq!(p.work_phone_area_code(), Some(61)); }
コンビネータ:map
match
はOption
を扱うのに適したメソッドです。しかし、大量にこれを使用しているとじきに億劫になってくるでしょう。引数の値が有効である(訳注: この場合はNone
でない)必要がある関数を扱う際には特にそうです。そうした場合には、コンビネータを使うと、処理の流れをモジュール化されたやり方で管理できます。
Some -> Some
あるいはNone -> None
の単純な操作を適用する必要がある場合には、Option
はmap()
というビルトインのメソッドを提供していますので、これを使用しましょう。map()
のフレキシビリティは、複数の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, } } // 上と同じように、食べ物を切る前に、皮を向いた食べ物の有無を知る必要があります。 fn chop(peeled: Option<Peeled>) -> Option<Chopped> { match peeled { Some(Peeled(food)) => Some(Chopped(food)), None => None, } } // 上のチェックと同様ですが`match`の代わりに`map()`を使用しています。 fn cook(chopped: Option<Chopped>) -> Option<Cooked> { chopped.map(|Chopped(food)| Cooked(food)) } // 複数の`map()`をチェインさせて、上のプロセスをシンプルにすることもできます。 fn process(food: Option<Food>) -> Option<Cooked> { food.map(|f| Peeled(f)) .map(|Peeled(f)| Chopped(f)) .map(|Chopped(f)| Cooked(f)) } // 食べる前に、食べ物の有無をチェックするのは大事ですよね! fn eat(food: Option<Cooked>) { match food { Some(food) => println!("Mmm. I love {:?}", food), None => println!("Oh no! It wasn't edible."), } } fn main() { let apple = Some(Food::Apple); let carrot = Some(Food::Carrot); let potato = None; let cooked_apple = cook(chop(peel(apple))); let cooked_carrot = cook(chop(peel(carrot))); // よりシンプルな見た目の`process()`を使用しましょう。 let cooked_potato = process(potato); eat(cooked_apple); eat(cooked_carrot); eat(cooked_potato); }
参照
コンビネータ:and_then
先ほどはmap()
を、チェイン構文を用いてmatch
文を単純化する物として説明しました。しかしOption<T>
を返す関数に対してのmap()
の使用はネストしたOption<Option<T>>
を生じさせます。ですので、複数の関数呼び出しをチェインさせることは混乱を招く場合があります。そんな時こそand_then()
の出番です。他の言語ではflatmapと呼ばれることもあります。
and_then()
は引数として与えられた関数にラップされた値を渡しますが、その値が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 } // 我々は寿司の材料を持っていません。 fn have_ingredients(food: Food) -> Option<Food> { match food { Food::Sushi => None, _ => Some(food), } } // コルドン・ブルーのレシピも持っていません。 fn have_recipe(food: Food) -> Option<Food> { match food { Food::CordonBleu => None, _ => Some(food), } } // 料理を作るためには、材料とレシピの両方が必要。 // ロジックの流れを`match`のチェインで表します。 fn cookable_v1(food: Food) -> Option<Food> { match have_recipe(food) { None => None, Some(food) => have_ingredients(food), } } // `and_then()`を用いることで、同じことをよりコンパクトに表現できます。 fn cookable_v3(food: Food) -> Option<Food> { have_recipe(food).and_then(have_ingredients) } // Otherwise we'd need to `flatten()` an `Option<Option<Food>>` // to get an `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!("Yay! On {:?} we get to eat {:?}.", day, food), None => println!("Oh no. We don't get to eat on {:?}?", day), } } fn main() { let (cordon_bleu, steak, sushi) = (Food::CordonBleu, Food::Steak, Food::Sushi); eat(cordon_bleu, Day::Monday); eat(steak, Day::Tuesday); eat(sushi, Day::Wednesday); }
参照
クロージャ, Option
, Option::and_then()
, Option::flatten()
Unpacking options and defaults
There is more than one way to unpack an Option
and fall back on a default if it is None
. To choose the one that meets our needs, we need to consider the following:
- do we need eager or lazy evaluation?
- do we need to keep the original empty value intact, or modify it in place?
or()
is chainable, evaluates eagerly, keeps empty value intact
or()
is chainable and eagerly evaluates its argument, as is shown in the following example. Note that because or
's arguments are evaluated eagerly, the variable passed to or
is moved.
#[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: {:?}", first_available_fruit); // first_available_fruit: Some(Orange) // `or` moves its argument. // In the example above, `or(orange)` returned a `Some`, so `or(apple)` was not invoked. // But the variable named `apple` has been moved regardless, and cannot be used anymore. // println!("Variable apple was moved, so this line won't compile: {:?}", apple); // TODO: uncomment the line above to see the compiler error }
or_else()
is chainable, evaluates lazily, keeps empty value intact
Another alternative is to use or_else
, which is also chainable, and evaluates lazily, as is shown in the following example:
#[derive(Debug)] enum Fruit { Apple, Orange, Banana, Kiwi, Lemon } fn main() { let no_fruit: Option<Fruit> = None; let get_kiwi_as_fallback = || { println!("Providing kiwi as fallback"); Some(Fruit::Kiwi) }; let get_lemon_as_fallback = || { println!("Providing lemon as fallback"); 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: {:?}", first_available_fruit); // Providing kiwi as fallback // first_available_fruit: Some(Kiwi) }
get_or_insert()
evaluates eagerly, modifies empty value in place
To make sure that an Option
contains a value, we can use get_or_insert
to modify it in place with a fallback value, as is shown in the following example. Note that get_or_insert
eagerly evaluates its parameter, so variable apple
is moved:
#[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 is: {:?}", first_available_fruit); println!("my_fruit is: {:?}", my_fruit); // first_available_fruit is: Apple // my_fruit is: Some(Apple) //println!("Variable named `apple` is moved: {:?}", apple); // TODO: uncomment the line above to see the compiler error }
get_or_insert_with()
evaluates lazily, modifies empty value in place
Instead of explicitly providing a value to fall back on, we can pass a closure to get_or_insert_with
, as follows:
#[derive(Debug)] enum Fruit { Apple, Orange, Banana, Kiwi, Lemon } fn main() { let mut my_fruit: Option<Fruit> = None; let get_lemon_as_fallback = || { println!("Providing lemon as fallback"); Fruit::Lemon }; let first_available_fruit = my_fruit .get_or_insert_with(get_lemon_as_fallback); println!("first_available_fruit is: {:?}", first_available_fruit); println!("my_fruit is: {:?}", my_fruit); // Providing lemon as fallback // first_available_fruit is: Lemon // my_fruit is: Some(Lemon) // If the Option has a value, it is left unchanged, and the closure is not invoked 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 is: {:?}", should_be_apple); println!("my_apple is unchanged: {:?}", my_apple); // The output is a follows. Note that the closure `get_lemon_as_fallback` is not invoked // should_be_apple is: Apple // my_apple is unchanged: Some(Apple) }
参照
closures
, get_or_insert
, get_or_insert_with
, moved variables
, 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
型を返すメソッドを目にするでしょう。文字列を他の型にパースすることは必ずしも成功する訳ではないため、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
はプログラムを終了させて不快なエラーメッセージを出力します。
エラーメッセージを改善するために、リターン型に対してもっと明確になるべきで、またエラーを明示的に処理することを考えるべきです。
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 is {}", n), Err(e) => println!("Error: {}", e), } } fn main() { // ここは以前と変わらず、妥当な解を与えます。 let twenty = multiply("10", "2"); print(twenty); // こちらは今度は有益なエラーメッセージを与えます。 let tt = multiply("t", "2"); print(tt); }
幸運にも、Option
のmap
、and_then
、その他多くのコンビネータもResult
のために実装されています。Result
に全てのリストが記載されています。
use std::num::ParseIntError; // `Option`と同様、`map()`などのコンビネータを使うことができます。 // この関数は`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 is {}", n), Err(e) => println!("Error: {}", e), } } fn main() { // ここは以前と変わらず、妥当な解を与えます。 let twenty = multiply("10", "2"); print(twenty); // こちらは今度は有益なエラーメッセージを与えます。 let tt = multiply("t", "2"); print(tt); }
Result
に対するエイリアス
特定のResult
型を何度も使いたくなるのはどんな時でしょう?Rustはエイリアスの作成をサポートしていたことを思い出してください。便利なことに、特定のResult
型に対しても定義することができます。
モジュールレベルでは、エイリアスの作成は非常に役に立ちます。特定のモジュールで見られるエラーは同じErr
型を持つため、単一のエイリアスで簡潔にResults
に関わる_全て_を定義できます。std
ライブラリが提供するもの(io::Result
)もあるほど有益なのです!
簡単な例で構文を見てみましょう。
use std::num::ParseIntError; // `ParseIntError`を`Err`の型として持つ全ての`Result`のジェネリックエイリアス type AliasedResult<T> = Result<T, ParseIntError>; // 上で定義したエイリアス(この場所特有の`Result`型)を使用。 fn multiply(first_number_str: &str, second_number_str: &str) -> AliasedResult<i32> { first_number_str.parse::<i32>().and_then(|first_number| { second_number_str.parse::<i32>().map(|second_number| first_number * second_number) }) } // もう一度使用。エイリアスによって再度明記する必要性がありません。 fn print(result: AliasedResult<i32>) { match result { Ok(n) => println!("n is {}", n), Err(e) => println!("Error: {}", e), } } fn main() { print(multiply("10", "2")); print(multiply("t", "2")); }
参照
早期リターン
前の例では、コンビネータの活用によりエラーを明示的に処理しました。場合分けに対する別の対処法として、match
文と早期リターンを組み合わせて使うこともできます。
つまり、エラーが発生した時点で関数の実行を止め、エラーを返してしまうという単純な方法が使えるということです。この方法の方がより読みやすく書きやすい場合があります。早期リターンを使って実装された、前の例の新たなバージョンを考えてみましょう。
use std::num::ParseIntError; fn multiply(first_number_str: &str, second_number_str: &str) -> Result<i32, ParseIntError> { let first_number = match first_number_str.parse::<i32>() { Ok(first_number) => first_number, Err(e) => return Err(e), }; let second_number = match second_number_str.parse::<i32>() { Ok(second_number) => second_number, Err(e) => return Err(e), }; Ok(first_number * second_number) } fn print(result: Result<i32, ParseIntError>) { match result { Ok(n) => println!("n is {}", n), Err(e) => println!("Error: {}", e), } } fn main() { print(multiply("10", "2")); print(multiply("t", "2")); }
ここまでで、コンビネータと早期リターンによる明示的なエラーハンドリングについて学びました。しかし、パニックは一般に避けたいですが、全てのエラーを明示的に処理するのも厄介でしょう。
次の節では、panic
を発生させずにunwrap
する必要があるケースのための?
について紹介していきます。
?
の導入
時にはpanic
の可能性を無視して、unwrap
のシンプルさを活用したいこともあるでしょう。今までのunwrap
は、値を_取り出す_ためだけであろうとも、ネストを深く書くことを要求しました。そして、これがまさに?
の目的です。
Err
を見つけるにあたり、2つのとるべき行動があります。
- 可能な限り避けたいと決めた
panic!
Err
は処理できないことを意味するためreturn
?
は_ほぼ_1まさしく、Err
に対してpanic
するよりreturn
するという点でunwrap
と同等です。コンビネータを使った以前の例をどれだけ簡潔に書けるか見てみましょう。
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!
に出会うこともあります。try!
を使って前の例と同じmultiply
関数を実装すると、以下のようになるでしょう。
// 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:要素が数字としてパースできない。 }
There are times when we'll want to stop processing on errors (like with ?
) but keep going when the Option
is None
. The transpose
function comes in handy to swap the Result
and 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, "invalid first item to double") } } fn double_first(vec: Vec<&str>) -> Result<i32> { vec.first() // エラーを新たな型に変更します。 .ok_or(DoubleError) .and_then(|s| { s.parse::<i32>() // ここでも新たなエラー型に更新します。 .map_err(|_| DoubleError) .map(|i| 2 * i) }) } fn print(result: Result<i32>) { match result { Ok(n) => println!("The first doubled is {}", n), Err(e) => println!("Error: {}", e), } } fn main() { let numbers = vec!["42", "93", "18"]; let empty = vec![]; let strings = vec!["tofu", "93", "18"]; print(double_first(numbers)); print(double_first(empty)); print(double_first(strings)); }
エラーをBox
する
元のエラーを維持しながらシンプルなコードを書くには、Box
してしまうと良いでしょう。欠点として、元のエラー型はランタイムまで判明せず、静的に決定されないことが挙げられます。
標準ライブラリはBox
に、From
を介してあらゆるError
トレイトを実装した型からBox<Error>
トレイトオブジェクトへの変換を実装させることで、エラーをboxしやすくしてくれます。
use std::error; use std::fmt; // エイリアスを`Box<error::Error>`に変更します。 type Result<T> = std::result::Result<T, Box<dyn error::Error>>; #[derive(Debug, Clone)] struct EmptyVec; impl fmt::Display for EmptyVec { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "invalid first item to double") } } impl error::Error for EmptyVec {} fn double_first(vec: Vec<&str>) -> Result<i32> { vec.first() .ok_or_else(|| EmptyVec.into()) // Boxに変換。 .and_then(|s| { s.parse::<i32>() .map_err(|e| e.into()) // 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
の呼び出しに対するその場での対応として、エラーをライブラリのエラーからboxされたエラーへと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<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`と`Option`を繋げていく代わりに、 // `?`で内部の値をその場で取得します。 fn double_first(vec: Vec<&str>) -> Result<i32> { let first = vec.first().ok_or(EmptyVec)?; let parsed = first.parse::<i32>()?; Ok(2 * parsed) } fn print(result: Result<i32>) { match result { Ok(n) => println!("The first doubled is {}", n), Err(e) => println!("Error: {}", e), } } fn main() { let numbers = vec!["42", "93", "18"]; let empty = vec![]; let strings = vec!["tofu", "93", "18"]; print(double_first(numbers)); print(double_first(empty)); print(double_first(strings)); }
これでかなり綺麗になりました。元のpanic
と比べ、リターン型がResult
であることを除けば、unwrap
の呼び出しを?
で置き換えたものに非常に似ています。結果、そのResult
は上のレベルで分解されなければなりません。
参照
エラーをラップする
Boxする方法の代替として、エラーを自前のエラー型としてラップする方法もあります。
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, "please use a vector with at least one element"), // ラップされたエラーは追加情報を含み、 // source メソッドから取り出すことができます。 DoubleError::Parse(..) => write!(f, "the provided string could not be parsed as 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)?; // Here we implicitly use the `ParseIntError` implementation of `From` (which // we defined above) in order to create a `DoubleError`. 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); if let Some(source) = e.source() { println!(" Caused by: {}", 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); }
Collect the failed items with map_err()
and filter_map()
map_err
calls a function with the error, so by adding that to the previous filter_map
solution we can save them off to the side while iterating.
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
は、それらのベクタ(Vec<Result<T, E>>
)からベクタのそれ(Result<Vec<T>, E>
)へと変換できるようにするため、FromIterator
を実装します。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
ライブラリは、基本データ型を劇的に拡張するカスタム型を数多く提供します。例えば以下です。
- 拡張可能な文字列である
String
。例えば:"hello world"
- growable vectors:
[1, 2, 3]
- オプション型:
Option<i32>
- エラーハンドリング用の
Result<i32, i32>
- ヒープ上の資源へのポインタ:
Box<i32>
参照
Box、スタックとヒープ
Rustにおいて、すべての値はデフォルトでスタックに割り当てられます。Box<T>
を作成することで、値を ボックス化 、すなわちヒープ上に割り当てることができます。ボックスとは正確にはヒープ上におかれたT
の値へのスマートポインタです。ボックスがスコープを抜けると、デストラクタが呼ばれて内包するオブジェクトが破棄され、ヒープメモリが解放されます。
ボックス化された値は*
演算子を用いてデリファレンスすることができます。これにより一段と直接的な操作が可能になります。
use std::mem; #[allow(dead_code)] #[derive(Debug, Clone, Copy)] struct Point { x: f64, y: f64, } // 長方形は左上の角と右下の角がどこにあるかによって特定できます。 #[allow(dead_code)] struct Rectangle { top_left: Point, bottom_right: Point, } fn origin() -> Point { Point { x: 0.0, y: 0.0 } } fn boxed_origin() -> Box<Point> { // このPointをヒープ上に割り当て、ポインタを返します。 Box::new(Point { x: 0.0, y: 0.0 }) } fn main() { // (以下では型を全て明示していますが、必須ではありません。) // この変数ははすべてスタック上に割り当てられます。 let point: Point = origin(); let rectangle: Rectangle = Rectangle { top_left: origin(), bottom_right: Point { x: 3.0, y: -4.0 } }; // ヒープ上に割り当てられたRectangle let boxed_rectangle: Box<Rectangle> = Box::new(Rectangle { top_left: origin(), bottom_right: Point { x: 3.0, y: -4.0 }, }); // 関数の返り値をボックス化。 let boxed_point: Box<Point> = Box::new(origin()); // 間にもう一つポインタを挟みます。 let box_in_a_box: Box<Box<Point>> = Box::new(boxed_origin()); println!("Point occupies {} bytes on the stack", mem::size_of_val(&point)); println!("Rectangle occupies {} bytes on the stack", mem::size_of_val(&rectangle)); // ボックスのサイズはポインタのサイズに等しい。 println!("Boxed point occupies {} bytes on the stack", mem::size_of_val(&boxed_point)); println!("Boxed rectangle occupies {} bytes on the stack", mem::size_of_val(&boxed_rectangle)); println!("Boxed box occupies {} bytes on the stack", mem::size_of_val(&box_in_a_box)); // `boxed_point`の保持するデータを`unboxed_point`にコピーします。 let unboxed_point: Point = *boxed_point; println!("Unboxed point occupies {} bytes on the stack", 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()); // 不正なインデックスアクセスはpanicを引き起こします。 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
は有効なUTF-8の配列であることを保証されたバイトのベクタ(Vec<u8>
)として保持されます。ヒープ上に保持され、伸長可能で、末端にnull文字を含みません。
&str
は有効なUTF-8の配列のスライス(&[u8]
)で、いつでもString
に変換することができます。&[T]
がいつでもVec<T>
に変換できるのと同様です。
fn main() { // (以下の例では型を明示していますが、これらは必須ではありません。) // read only memory上に割り当てられた文字列への参照 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モジュールを参照してください。
Literals and escapes
There are multiple ways to write string literals with special characters in them. All result in a similar &str
so it's best to use the form that is the most convenient to write. Similarly there are multiple ways to write byte string literals, which all result in &[u8; N]
.
Generally special characters are escaped with a backslash character: \
. This way you can add any character to your string, even unprintable ones and ones that you don't know how to type. If you want a literal backslash, escape it with another one: \\
String or character literal delimiters occurring within a literal must be escaped: "\""
, '\''
.
fn main() { // You can use escapes to write bytes by their hexadecimal values... let byte_escape = "I'm writing \x52\x75\x73\x74!"; println!("What are you doing\x3F (\\x3F means ?) {}", byte_escape); // ...or Unicode code points. 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); }
Sometimes there are just too many characters that need to be escaped or it's just much more convenient to write a string out as-is. This is where raw string literals come into play.
fn main() { let raw_str = r"Escapes don't work here: \x3F \u{211D}"; println!("{}", raw_str); // If you need quotes in a raw string, add a pair of #s let quotes = r#"And then I said: "There is no escape!""#; println!("{}", quotes); // If you need "# in your string, just use more #s in the delimiter. // You can use up to 255 #s. let longer_delimiter = r###"A string with "# in it. And even "##!"###; println!("{}", longer_delimiter); }
Want a string that's not UTF-8? (Remember, str
and String
must be valid UTF-8). Or maybe you want an array of bytes that's mostly text? Byte strings to the rescue!
use std::str; fn main() { // Note that this is not actually a `&str` let bytestring: &[u8; 21] = b"this is a byte string"; // Byte arrays don't have the `Display` trait, so printing them is a bit limited println!("A byte string: {:?}", bytestring); // Byte strings can have byte escapes... let escaped = b"\x52\x75\x73\x74 as bytes"; // ...but no unicode escapes // let escaped = b"\u{211D} is not allowed"; println!("Some escaped bytes: {:?}", escaped); // Raw byte strings work just like raw strings let raw_bytestring = br"\u{211D} is not escaped here"; println!("{:?}", raw_bytestring); // Converting a byte array to `str` can fail 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"#; // Byte strings don't have to be UTF-8 let shift_jis = b"\x82\xe6\x82\xa8\x82\xb1\x82\xbb"; // "ようこそ" in SHIFT-JIS // But then they can't always be converted to `str` match str::from_utf8(shift_jis) { Ok(my_str) => println!("Conversion successful: '{}'", my_str), Err(e) => println!("Conversion failed: {:?}", e), }; }
For conversions between character encodings check out the encoding crate.
A more detailed listing of the ways to write string literals and escape characters is given in the 'Tokens' chapter of the Rust Reference.
Option
プログラムの一部が失敗した際、panic!
するよりも、エラーを捕捉する方が望ましい場合があります。これはOption
という列挙型を用いることで可能になります。
列挙型Option<T>
には2つの値があります。
None
、これは実行の失敗か値の欠如を示します。Some(value)
、型T
のvalue
をラップするタプルです。
// `panic!`を起こさない整数の割り算 fn checked_division(dividend: i32, divisor: i32) -> Option<i32> { if divisor == 0 { // 失敗は`None`としてあらわされます。 None } else { // 結果は`Some`にラップされます。 Some(dividend / divisor) } } // この関数は失敗する割り算を扱うことができます。 fn try_division(dividend: i32, divisor: i32) { // `Option` の値は、他のあらゆる列挙型と同様パターンマッチに使用できます。 match checked_division(dividend, divisor) { None => println!("{} / {} failed!", dividend, divisor), Some(quotient) => { println!("{} / {} = {}", dividend, divisor, quotient) }, } } fn main() { try_division(4, 2); try_division(1, 0); // `None`を変数に代入する際は、型を明示しなくてはなりません。 let none: Option<i32> = None; let _equivalent_none = None::<i32>; let optional_float = Some(0f32); // `Some`をアンラップすると中の値を取得できます。 println!("{:?} unwraps to {:?}", optional_float, optional_float.unwrap()); // `None`をアンラップしようとすると`panic!`します。 println!("{:?} unwraps to {:?}", none, none.unwrap()); }
Result
これまでの例で、失敗する可能性のある関数の返り値として、列挙型Option
が使用でき、失敗時の返り値にはNone
を用いることを見てきました。しかし、時には なぜ そのオペレーションが失敗したのかを明示することが重要な場合があります。そのためにはResult
列挙型を使用します。
列挙型Result<T, E>
は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 { // 分母が0なので、このオペレーションは普通に行えば失敗します。 // 代わりに`Err`でラップされた失敗の理由を返しましょう。 Err(MathError::DivisionByZero) } else { // このオペレーションは問題がないので、 // 結果を`Ok`でラップして返しましょう。 Ok(x / y) } } pub fn sqrt(x: f64) -> MathResult { if x < 0.0 { Err(MathError::NegativeSquareRoot) } else { Ok(x.sqrt()) } } pub fn ln(x: f64) -> MathResult { if x <= 0.0 { Err(MathError::NonPositiveLogarithm) } else { Ok(x.ln()) } } } // `op(x, y)` === `sqrt(ln(x / y))` fn op(x: f64, y: f64) -> f64 { // 3段階の`match`ピラミッド! match checked::div(x, y) { Err(why) => panic!("{:?}", why), Ok(ratio) => match checked::ln(ratio) { Err(why) => panic!("{:?}", why), Ok(ln) => match checked::sqrt(ln) { Err(why) => panic!("{:?}", why), Ok(sqrt) => sqrt, }, }, } } fn main() { // これは失敗するでしょうか? println!("{}", op(1.0, 10.0)); }
?
マッチを利用して結果をチェインするのは中々面倒です。幸いなことに、?
マクロを使用すればイケてるコードに戻すことができます。?
はResult
を返す式の末尾で使います。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 => "logarithm of non-positive number", MathError::DivisionByZero => "division by zero", MathError::NegativeSquareRoot => "square root of negative number", }), Ok(value) => println!("{}", value), } } } fn main() { checked::op(1.0, 10.0); }
公式ドキュメントをチェックすることをオススメします。Result
型を扱う関数やResult
型のメソッドが多く挙げられています。
panic!
panic!
マクロはパニックを生成し、スタックの巻き戻しを開始するために使用することができます。巻き戻しの間、ランタイムは、(訳注: panicを起こした)スレッドが 所有権を持つ 全ての資源のデストラクタを呼び出し、メモリ上から解放します。
今回はシングルスレッドのプログラムを実行しているので、panic!
はプログラムにパニックメッセージを表示させ、exitします。
// 整数の除法(/)の再実装 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
トレイトを保持する型なら何でもOKです。次のセクションでより詳しく見ていきます。
ベクタ型と同様、伸長可能ですが、HashMap
の場合さらに、スペースが余っているときには小さくすることも可能です。HashMap
を一定の容量のエリアに作成するときはHashMap::with_capacity(uint)
を、デフォルトの容量で作成するときはHashMap::new()
を用います。後者が推奨されています。
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()`は // 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)); } }
ハッシングやハッシュマップ(ハッシュテーブルと呼ばれることもあります)の仕組みについて、より詳しく知りたい場合はWikipediaのハッシュテーブルのページを見てください。
キー型の変種
Eq
とHash
トレイトを実装している型ならば、なんでもHashMap
のキーになることができます。例えば以下です。
bool
(キーになりうる値が2つしかないので実用的ではないですが…)int
、uint
、あるいは他の整数型String
と&str
(Tips:String
をキーにしたハッシュマップを作製した場合、.get()
メソッドの引数に&str
を与えて値を取得することができます。)
f32
とf64
はHash
を実装して いない ことに注意しましょう。おそらくこれは浮動小数点演算時に誤差が発生するため、キーとして使用すると、恐ろしいほどエラーの元となるためです。
集合型は、その要素となっている全ての型がEq
を、あるいはHash
を実装している場合、必ず同じトレイトを実装しています。例えば、Vec<T>
はT
がHash
を実装している場合、Hash
を実装します。
独自の型にEq
あるいはHash
を実装するのは簡単です。以下の一行で済みます。#[derive(PartialEq, Eq, Hash)]
後はコンパイラがよしなにしてくれます。これらのトレイトの詳細をコントロールしたい場合、Eq
やHash
を自分で実装することもできます。この文書ではHash
トレイトを実装する方法の詳細については触れません。
struct
をHashMap
で扱う際の例として、とてもシンプルなユーザーログインシステムを作成してみましょう。
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: {}", username); println!("Password: {}", password); println!("Attempting logon..."); let logon = Account { username, password, }; match accounts.get(&logon) { Some(account_info) => { println!("Successful logon!"); println!("Name: {}", account_info.name); println!("Email: {}", account_info.email); }, _ => println!("Login failed!"), } } fn main(){ let mut accounts: Accounts = HashMap::new(); let account = Account { username: "j.everyman", password: "password123", }; let account_info = AccountInfo { name: "John Everyman", email: "j.everyman@email.com", }; accounts.insert(account, account_info); try_logon(&accounts, "j.everyman", "psasword123"); try_logon(&accounts, "j.everyman", "password123"); }
ハッシュ集合
値がなく、キーだけのHashMap
を想像してみてください。これはハッシュ集合(HashSet
)と呼ばれるものです。(HashSet<T>
は、実際にはHashMap<T, ()>
のラッパーです。)
「何の意味があるの?フツーにキーをVec
に入れればいいじゃん」そう思いましたね?
それは、HashSet
独自の機能として、要素に重複がないということが保証されるためです。これは全ての集合型がもつ機能です。HashSet
はその実装の1つであり、他にはBTreeSet
等があります。
HashSet
に、すでに存在する値を加えようとすると、(すなわち、加えようとしている値のハッシュ値と、要素中のいずれかの値のハッシュ値が等しい場合、)新しい値によって古い値が上書きされます。
これは、同じ値を2つ以上欲しくない場合や、すでにある値を持っているか知りたい場合にとても有効です。
しかし、集合型の機能はそれだけではありません。
集合型には4つの主要なメソッドがあり、(すべてイテレータを返します。)
-
union
:2つの集合型のどちらか一方にある値を全て取得。 -
difference
:1つ目の集合にあり、かつ2つ目には存在しない値を全て取得。 -
intersection
:両方の集合にある値のみを取得。 -
symmetric_difference
:どちらか一方の集合には存在するが、両方には ない 値を取得。
以下の例でこれらをすべて見ていきましょう。
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
When multiple ownership is needed, Rc
(Reference Counting) can be used. Rc
keeps track of the number of the references which means the number of owners of the value wrapped inside an Rc
.
Reference count of an Rc
increases by 1 whenever an Rc
is cloned, and decreases by 1 whenever one cloned Rc
is dropped out of the scope. When an Rc
's reference count becomes zero (which means there are no remaining owners), both the Rc
and the value are all dropped.
Cloning an Rc
never performs a deep copy. Cloning creates just another pointer to the wrapped value, and increments the count.
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)); // Two `Rc`s are equal if their inner values are equal println!("rc_a and rc_b are equal: {}", rc_a.eq(&rc_b)); // We can use methods of a value directly 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 ---"); } // Error! `rc_examples` already moved into `rc_a` // And when `rc_a` is dropped, `rc_examples` is dropped together // println!("rc_examples: {}", rc_examples); // TODO ^ Try uncommenting this line }
参照
std::rc and std::sync::arc.
Arc
When shared ownership between threads is needed, Arc
(Atomically Reference Counted) can be used. This struct, via the Clone
implementation can create a reference pointer for the location of a value in the memory heap while increasing the reference counter. As it shares ownership between threads, when the last reference pointer to a value is out of scope, the variable is dropped.
use std::time::Duration; use std::sync::Arc; use std::thread; fn main() { // This variable declaration is where its value is specified. let apple = Arc::new("the same apple"); for _ in 0..10 { // Here there is no value specification as it is a pointer to a // reference in the memory heap. let apple = Arc::clone(&apple); thread::spawn(move || { // As Arc was used, threads can be spawned using the value allocated // in the Arc variable pointer's location. println!("{:?}", apple); }); } // Make sure all Arc instances are printed from spawned threads. thread::sleep(Duration::from_secs(1)); }
標準ライブラリのその他
他にも、様々な型がstdライブラリの中で提供されています。例えば以下の機能を果たすための物があります。
- スレッド
- チャネル
- ファイル I/O
これらにより基本データ型の提供する機能よりも遥かに豊かなことが実現できます。
参照
スレッド
Rustはspawn
関数を用いてOSのネイティブスレッドを開始することができます。この関数の引数はmoveクロージャ(訳注: 参照ではなく値を取るクロージャ)です。
use std::thread; const NTHREADS: u32 = 10; // この関数は`main`スレッドで実行されます。 fn main() { // spawnされるクロージャを保持するためのベクタ let mut children = vec![]; for i in 0..NTHREADS { // 新しいスレッドを起動。 children.push(thread::spawn(move || { println!("this is thread number {}", i); })); } for child in children { // 子スレッドが終了するのを待ち、結果を返します。 let _ = child.join(); } }
これらのスレッドのスケジューリングはOSによって行われます。
テストケース:map-reduce
Rust makes it very easy to parallelize data processing, without many of the headaches traditionally associated with such an attempt.
The standard library provides great threading primitives out of the box. These, combined with Rust's concept of Ownership and aliasing rules, automatically prevent data races.
The aliasing rules (one writable reference XOR many readable references) automatically prevent you from manipulating state that is visible to other threads. (Where synchronization is needed, there are synchronization primitives like Mutex
es or Channel
s.)
In this example, we will calculate the sum of all digits in a block of numbers. We will do this by parcelling out chunks of the block into different threads. Each thread will sum its tiny block of digits, and subsequently we will sum the intermediate sums produced by each thread.
Note that, although we're passing references across thread boundaries, Rust understands that we're only passing read-only references, and that thus no unsafety or data races can occur. Also because the references we're passing have 'static
lifetimes, Rust understands that our data won't be destroyed while these threads are still running. (When you need to share non-static
data between threads, you can use a smart pointer like Arc
to keep the data alive and avoid non-static
lifetimes.)
use std::thread; // この関数は`main`スレッドで実行されます。 fn main() { // This is our data to process. // We will calculate the sum of all digits via a threaded map-reduce algorithm. // Each whitespace separated chunk will be handled in a different thread. // // TODO: see what happens to the output if you insert spaces! let data = "86967897737416471853297327050364959 11861322575564723963297542624962850 70856234701860851907960690014725639 38397966707106094172783238747669219 52380795257888236525459303330302837 58495327135744041048897885734297812 69920216438980873548808413720956532 16278424637452589860345374828574668"; // Make a vector to hold the child-threads which we will spawn. let mut children = vec![]; /************************************************************************* * "Map" phase * * Divide our data into segments, and apply initial processing ************************************************************************/ // split our data into segments for individual calculation // each chunk will be a reference (&str) into the actual data let chunked_data = data.split_whitespace(); // Iterate over the data segments. // .enumerate() adds the current loop index to whatever is iterated // the resulting tuple "(index, element)" is then immediately // "destructured" into two variables, "i" and "data_segment" with a // "destructuring assignment" for (i, data_segment) in chunked_data.enumerate() { println!("data segment {} is \"{}\"", i, data_segment); // Process each data segment in a separate thread // // spawn() returns a handle to the new thread, // which we MUST keep to access the returned value // // 'move || -> u32' is syntax for a closure that: // * takes no arguments ('||') // * takes ownership of its captured variables ('move') and // * returns an unsigned 32-bit integer ('-> u32') // // Rust is smart enough to infer the '-> u32' from // the closure itself so we could have left that out. // // TODO: try removing the 'move' and see what happens children.push(thread::spawn(move || -> u32 { // Calculate the intermediate sum of this segment: let result = data_segment // iterate over the characters of our segment.. .chars() // .. convert text-characters to their number value.. .map(|c| c.to_digit(10).expect("should be a digit")) // .. and sum the resulting iterator of numbers .sum(); // println! locks stdout, so no text-interleaving occurs println!("processed segment {}, result={}", i, result); // "return" not needed, because Rust is an "expression language", the // last evaluated expression in each block is automatically its value. result })); } /************************************************************************* * "Reduce" phase * * Collect our intermediate results, and combine them into a final result ************************************************************************/ // combine each thread's intermediate results into a single final sum. // // we use the "turbofish" ::<> to provide sum() with a type hint. // // TODO: try without the turbofish, by instead explicitly // specifying the type of final_result let final_result = children.into_iter().map(|c| c.join().unwrap()).sum::<u32>(); println!("Final sum result: {}", final_result); }
Assignments
It is not wise to let our number of threads depend on user inputted data. What if the user decides to insert a lot of spaces? Do we really want to spawn 2,000 threads? Modify the program so that the data is always chunked into a limited number of chunks, defined by a static constant at the beginning of the program.
参照
- スレッド
- ベクタ型, イテレータ
- クロージャ, move semantics and
move
クロージャ - デストラクト 代入
- 型推論を補助する ターボフィッシュ記法
- unwrap と expect
- enumerate
チャネル
Rustは、スレッド間のコミュニケーションのために、非同期のチャネルを提供しています。チャネルは2つのエンドポイント、すなわち送信者と受信者を介して、情報の一方向への流れを作り出すことを可能にしています。
use std::sync::mpsc::{Sender, Receiver}; use std::sync::mpsc; use std::thread; static NTHREADS: i32 = 3; fn main() { // チャネルには`Sender<T>`と`Receiver<T>`という2つのエンドポイントがあります。 // ここで、`T`は送信されるメッセージの型です。 // (型アノテーションは必須ではありません。) let (tx, rx): (Sender<i32>, Receiver<i32>) = mpsc::channel(); let mut children = Vec::new(); for id in 0..NTHREADS { // 送信者エンドポイントはコピーすることができます。 let thread_tx = tx.clone(); // ここでは、それぞれのスレッドが自身のIDを送信しています。 let child = thread::spawn(move || { // スレッドは`thread_tx`の所有権をとり、それぞれのスレッドは // メッセージをチャネルにキューイングします。 thread_tx.send(id).unwrap(); // 送信はノンブロッキングなオペレーションなので、 // メッセージを送信した後もすぐに実行を継続します。 println!("thread {} finished", id); }); children.push(child); } // ここで、全てのメッセージが収集されます。 let mut ids = Vec::with_capacity(NTHREADS as usize); for _ in 0..NTHREADS { // `recv`メソッドはチャネルからメッセージを取り出します。 // もし取り出せるメッセージが存在しない場合、`recv`は // 現在のスレッドをブロックします。 ids.push(rx.recv()); } // Wait for the threads to complete any remaining work for child in children { child.join().expect("oops! the child thread panicked"); } // メッセージが送信された順番を表示。 println!("{:?}", ids); }
ファイルパス
構造体Path
は、ファイルシステム中のパスを表します。Path
には2つの変種があります。UNIXライクなファイルシステムのためのposix::Path
と、Windows用のwindows::Path
です。それぞれプラットフォームに対応したPath
をエクスポートします。
Path
はOsStr
から作ることができます。そうすればそのパスが指すファイル・ディレクトリの情報を取得するためのメソッドがいくつか使えるようになります。
Path
はイミュータブルです。Path
の所有権ありのバージョンがPathBuf
です。Path
とPathBuf
の関係は、str
とString
の関係に似ています。PathBuf
はそのまま変更でき、Path
にデリファレンスすることができます。
Path
の実態はUTF-8の文字列 ではなく 、OsString
であることに注意しましょう。したがって、Path
を&str
に変換するのは無条件 ではなく 、失敗する可能性があります。それゆえOption
型が返されます。しかしPath
からOsString
あるいは&OsStr
への変換はそれぞれinto_os_string
とas_os_str
によって無条件でできます。
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!("new path is not a valid UTF-8 sequence"), Some(s) => println!("new path is {}", s), } }
他のPath
メソッド(posix::Path
とwindows::Path
)をチェックするのを忘れずに!それとMetadata
構造体も見ておくことをオススメします。
参照
ファイル I/O
File
構造体は開かれたファイルを表し(実際にはファイルディスクリプタのラッパーです)、読み込み・書き込み権限のどちらか一方、あるいは両方を提供します。
Since many things can go wrong when doing file I/O, all the File
methods return the io::Result<T>
type, which is an alias for Result<T, io::Error>
.
これはI/Oに関するオペレーションの失敗をより明瞭にします。このおかげでプログラマは直面した失敗を全て見ることができ、より生産的な方法でそれらを扱うことが可能になります。
open
open
関数を用いることで読み込み専用モードでファイルを開くことが可能です。
File
はファイルディスクリプタという資源を保持しており、drop
時にはファイルを閉じるところまで面倒を見てくれます。
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
fn main() {
// 目的ファイルに対する`Path`を作成
let path = Path::new("hello.txt");
let display = path.display();
// pathを読み込み専用モードで開きます。これは`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
が存在しないとか、読み込み権限がないとか、そういった状況で実行してみてください。)
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()
メソッドはファイルの各行のイテレータを返すので、インラインでマップを実行し結果を収集することもできます。そうすると、より簡潔で読みやすい表現となります。
#![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
をそれぞれto_string()
とString::from
を使って、所有権のあるString
型に変換しなければならない点に注意してください。
より効率的なやり方
ここでは、開いたFile
の所有権をBufReader
構造体に渡します。BufReader
は内部的なバッファを使い、中間のメモリ割り当てを削減します。
read_lines
を更新して、それぞれの行に対してメモリ上に新しいString
オブジェクトを割り当てるのではなく、イテレータを返すようにします。
use std::fs::File; use std::io::{self, BufRead}; use std::path::Path; fn main() { // hosts.txtファイルは現在のパスに存在しなければなりません。 if let Ok(lines) = read_lines("./hosts.txt") { // イテレータを消費し、Option型のStringを返します。 for line in lines.flatten() { println!("{}", line); } } } // 出力はResult型にラップされ、エラーをマッチできるようになります。 // ファイルの各行のReaderへのイテレータを返します。 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>
を引数にとるので、ジェネリックなread_lines
メソッドも、where
キーワードを使って、同じジェネリックな制約を持たせています。
この処理は、ファイルの中身全てをメモリ上のString
にするよりも効率的です。メモリ上にString
を作ると、より大きなファイルを取り扱う際に、パフォーマンスの問題につながります。
子プロセス
process::Output
構造体は終了したプロセスのアウトプットを表し、process::Command
構造体はプロセスの作成を行います。
use std::process::Command;
fn main() {
let output = Command::new("rustc")
.arg("--version")
.output().unwrap_or_else(|e| {
panic!("failed to execute process: {}", e)
});
if output.status.success() {
let s = String::from_utf8_lossy(&output.stdout);
print!("rustc succeeded and stdout was:\n{}", s);
} else {
let s = String::from_utf8_lossy(&output.stderr);
print!("rustc failed and stderr was:\n{}", s);
}
}
(余裕があれば、上の例でrustc
に不正なフラグを渡し、どうなるか見てみましょう)
パイプ
std::Child
構造体は実行中の子プロセスを表します。stdin
、stdout
、stderr
を介して表面化のプロセスとのやり取りを仲介します。
use std::io::prelude::*;
use std::process::{Command, Stdio};
static PANGRAM: &'static str =
"the quick brown fox 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`され、
// パイプはcloseされます。
//
// これは非常に重要です。というのもcloseしないと`wc`は
// 送った値の処理を開始しないからです。
// `stdout`フィールドも`Option<ChildStdout>`型なのでアンラップする必要があります
let mut s = String::new();
match process.stdout.unwrap().read_to_string(&mut s) {
Err(why) => panic!("couldn't read wc stdout: {}", why),
Ok(_) => print!("wc responded with:\n{}", s),
}
}
ドロップの延期
process::Child
が終了するのを待ちたい場合は、process::ExitStatus
を返すChild::wait
を呼び出さなくてはなりません。
use std::process::Command;
fn main() {
let mut child = Command::new("sleep").arg("5").spawn().unwrap();
let _result = child.wait().unwrap();
println!("reached end of main");
}
$ rustc wait.rs && ./wait
# `wait`は`sleep 5`コマンドが終了するまで5秒間実行され続けます。
reached end of main
ファイルシステムとのやり取り
std::fs
モジュールはファイルシステムとやり取りするための関数をいくつか持っています。
use std::fs;
use std::fs::{File, OpenOptions};
use std::io;
use std::io::prelude::*;
#[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
An alternative way to define the function cat
is with ?
notation:
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
を介して取得できます。これはそれぞれの引数を文字列として生成するイテレータを返します。
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"].
クレート
Alternatively, there are numerous crates that can provide extra functionality when creating command-line applications. One of the more popular command line argument crates being clap
.
引数のパース
matchを用いて簡単な引数をパースできます。
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."), } }, // コマンドが一つと引数が一つの場合 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(); } } }
If you named your program match_args.rs
and compile it like this rustc match_args.rs
, you can execute it as follows:
$ ./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のライブラリを呼び出すために他言語関数インターフェイス(Foreign Function Interface, 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;
}
// 型安全にするためのラッパ
fn cos(z: Complex) -> Complex {
unsafe { ccosf(z) }
}
fn main() {
// z = -1 + 0i
let z = Complex { re: -1., im: 0. };
// calling a foreign function is an unsafe operation
let z_sqrt = unsafe { csqrtf(z) };
println!("the square root of {:?} is {:?}", z, z_sqrt);
// calling safe API wrapped around unsafe operation
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 on doc-testing
ユニットテスト
テストは、テスト以外のコードが想定通りに動いているかを確かめるRustの関数です。一般にテスト関数は、準備をしてからテストしたいコードを実行し、そしてその結果が期待したものであるか確認します。
大抵の場合ユニットテストは#[cfg(test)]
アトリビュートを付けたtests
モジュールに配置されます。テスト関数には#[test]
アトリビュートを付与します。
テスト関数内部でパニックするとテストは失敗となります。次のようなマクロが用意されています。
assert!(expression)
- 式を評価した結果がfalse
であればパニックします。assert_eq!(left, right)
とassert_ne!(left, right)
- 左右の式を評価した結果が、それぞれ等しくなること、ならないことをテストします。
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
// 誤った加算をする関数がテストに通らないことを示します。
#[allow(dead_code)]
fn bad_add(a: i32, b: i32) -> i32 {
a - b
}
#[cfg(test)]
mod tests {
// 外部のスコープから(mod testsに)名前をインポートする便利なイディオム。
use super::*;
#[test]
fn test_add() {
assert_eq!(add(1, 2), 3);
}
#[test]
fn test_bad_add() {
// このアサーションはパニックして、テストは失敗します。
// プライベートな関数もテストすることができます。
assert_eq!(bad_add(1, 2), 3);
}
}
cargo test
でテストを実行できます。
$ cargo test
running 2 tests
test tests::test_bad_add ... FAILED
test tests::test_add ... ok
failures:
---- tests::test_bad_add stdout ----
thread 'tests::test_bad_add' panicked at 'assertion failed: `(left == right)`
left: `-1`,
right: `3`', src/lib.rs:21:8
note: Run with `RUST_BACKTRACE=1` for a backtrace.
failures:
tests::test_bad_add
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
テストと?
ここまでに例示したユニットテストは返り値の型を持っていませんでしたが、Rust 2018ではユニットテストがResult<()>
を返し、内部で?
を使えるようになりました!これにより、ユニットテストをさらに簡潔に記述できます。
fn sqrt(number: f64) -> Result<f64, String> { if number >= 0.0 { Ok(number.powf(0.5)) } else { Err("negative floats don't have square roots".to_owned()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_sqrt() -> Result<(), String> { let x = 4.0; assert_eq!(sqrt(x)?.powf(2.0), x); Ok(()) } }
詳細はエディションガイドを参照してください。
パニックをテストする
ある条件下でパニックすべき関数をテストするには、#[should_panic]
アトリビュートを使います。このアトリビュートはパニックメッセージをオプションの引数expected =
で受け取れます。パニックの原因が複数あるときに、想定した原因でパニックが発生したことを確認できます。
pub fn divide_non_zero_result(a: u32, b: u32) -> u32 {
if b == 0 {
panic!("Divide-by-zero error");
} else if a < b {
panic!("Divide result is zero");
}
a / b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_divide() {
assert_eq!(divide_non_zero_result(10, 2), 5);
}
#[test]
#[should_panic]
fn test_any_panic() {
divide_non_zero_result(1, 0);
}
#[test]
#[should_panic(expected = "Divide result is zero")]
fn test_specific_panic() {
divide_non_zero_result(1, 10);
}
}
テストを実行すると、次の結果を得られます。
$ cargo test
running 3 tests
test tests::test_any_panic ... ok
test tests::test_divide ... ok
test tests::test_specific_panic ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Doc-tests tmp-test-should-panic
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
実行するテストを指定する
cargo test
にテストの名前を与えると、そのテストだけが実行されます。
$ cargo test test_any_panic
running 1 test
test tests::test_any_panic ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out
Doc-tests tmp-test-should-panic
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
テスト名の一部を指定すると、それにマッチするすべてのテストが実行されます。
$ cargo test panic
running 2 tests
test tests::test_any_panic ... ok
test tests::test_specific_panic ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out
Doc-tests tmp-test-should-panic
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
テストを除外する
テストを実行から除外するには、#[ignore]
アトリビュートを使います。また、cargo test -- --ignored
で、除外したテストのみを実行できます。
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は正確性を重視しているので、コードブロックもコンパイルされ、テストとして使われます。
/// 最初の行には関数の機能の短い要約を書きます。
///
/// 以降で詳細なドキュメンテーションを記述します。コードブロックは三重のバッククォートで始まり、
/// 暗黙的に`fn main()`と`extern crate <クレート名>`で囲われます。
/// `doccomments`クレートをテストしたいときには、次のように記述します。
///
/// ```
/// let result = doccomments::add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
/// 一般的に、ドキュメンテーションコメントは
/// "Examples", "Panics", "Failures" という章から成ります。
///
/// 次の関数は除算を実行します。
///
/// # Examples
///
/// ```
/// let result = doccomments::div(10, 2);
/// assert_eq!(result, 5);
/// ```
///
/// # Panics
///
/// 第2引数がゼロであればパニックします。
///
/// ```rust,should_panic
/// // ゼロで除算するとパニックします
/// doccomments::div(10, 0);
/// ```
pub fn div(a: i32, b: i32) -> i32 {
if b == 0 {
panic!("Divide-by-zero error");
}
a / b
}
ドキュメンテーションコメント中のコードブロックは、cargo test
コマンドで自動的にテストされます。
$ cargo test
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Doc-tests doccomments
running 3 tests
test src/lib.rs - add (line 7) ... ok
test src/lib.rs - div (line 21) ... ok
test src/lib.rs - div (line 31) ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
ドキュメンテーションテストの目的
ドキュメンテーションテストの主な目的は、実行例を示すことであり、これは最も大切なガイドラインの一つにもなっています。これにより、ドキュメントの例を実際に動くコードとして使うことができます。しかしながら、main
が()
を返すために、?
を使うとコンパイルに失敗してしまいます。ドキュメンテーションでコードブロックの一部を隠す機能で、この問題に対処できます。つまり、fn try_main() -> Result<(), ErrorType>
を定義しておきながらそれを隠し、暗黙のmain
の内部でunwrap
するのです。複雑なので、例を見てみましょう。
/// ドキュメンテーションテストで、`try_main`を隠して使います。
///
/// ```
/// # // 行頭に `#` を置くと行が隠されるが、コンパイルには成功します。
/// # fn try_main() -> Result<(), String> { // ドキュメントの本体を囲う行
/// let res = doccomments::try_div(10, 2)?;
/// # Ok(()) // try_mainから値を返します
/// # }
/// # fn main() { // unwrap()を実行します。
/// # try_main().unwrap(); // try_mainを呼びunwrapすると、エラーの場合にパニックします。
/// # }
/// ```
pub fn try_div(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err(String::from("Divide-by-zero"))
} else {
Ok(a / b)
}
}
参照
- RFC505 ドキュメンテーションのスタイルについて
- API Guidelines ドキュメンテーションのガイドラインについて
統合テスト
ユニットテストは、独立したモジュールを一つずつテストするものであり、テストは小さく、プライベートなコードについてもテストすることができます。統合テストはクレートの外側にあるもので、他の外部のコードと同様に、パブリックなインタフェースだけを使います。統合テストの目的は、ライブラリのそれぞれのモジュールが連携して正しく動作するかどうかテストすることです。
Cargoは、src
ディレクトリと並んで配置されたtests
ディレクトリを統合テストとして扱います。
ファイルsrc/lib.rs
:
// `adder`という名前のクレートの内部で、次の関数を定義します。
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
テストを含むファイルtests/integration_test.rs
:
#[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.rs
:
pub fn setup() {
// 必要なファイル・ディレクトリの作成やサーバの起動といった準備を行うコードを記述します。
}
テストを含むファイルtests/integration_test.rs
:
// 共通のモジュールをインポートします。
mod common;
#[test]
fn test_add() {
// 共通のコードを利用します。
common::setup();
assert_eq!(adder::add(3, 2), 5);
}
モジュールをtests/common.rs
に記述することも可能ですが、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 依存関係の指定について
安全でない操作
この章の内容を見る前に、公式ドキュメントから引用した次の文章をお読みください。「コードベース中の、アンセーフな操作をするコードの量は、可能な限り小さく無くてはならない。」この戒めを頭に叩き込んだ上で、さあはじめましょう!Rustにおいて、アンセーフなブロックはコンパイラのチェックをスルーするために使われます。具体的には以下の4つの主要なユースケースがあります。
- 生ポインタのデリファレンス
- 安全でない関数やメソッドの呼び出し(FFI経由の関数の呼び出しを含む (詳細は 本書のFFIに関する説明 を参照ください))
- 静的なミュータブル変数へのアクセスや変更
- 安全でないトレイトの実装
生ポインタ
生ポインタ*
と参照&T
はよく似た機能を持ちますが、後者は必ず有効なデータを指していることが借用チェッカーによって保証されているので、常に安全です。生ポインタのデリファレンスはアンセーフなブロックでしか実行できません。
fn main() { let raw_p: *const u32 = &10; unsafe { assert!(*raw_p == 10); } }
安全でない関数呼び出し
関数は unsafe
として宣言できます。これはコンパイラの代わりにプログラマの責任で正しさを保証することを意味します。例として std::slice::from_raw_parts
があります。この関数は最初の要素へのポインタと長さを指定してスライスを作成します。
use std::slice; fn main() { let some_vector = vec![1, 2, 3, 4]; let pointer = some_vector.as_ptr(); let length = some_vector.len(); unsafe { let my_slice: &[u32] = slice::from_raw_parts(pointer, length); assert_eq!(some_vector.as_slice(), my_slice); } }
slice::from_raw_parts
は、次のふたつの仮定に基づいて処理します。ひとつは渡されたポインタが有効なメモリ位置を指していること、もうひとつはそのメモリに格納された値が正しい型であることです。この仮定を満たさない場合、プログラムの動作は不定となり、何が起こるかわかりません。
インラインアセンブリ
Rustは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!
マクロの第一引数に列挙されます。
入力と出力
何もしない命令を挿入しても面白くありません。実際にデータを操作してみましょう。
#![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); } }
これはu64
型の変数x
に5
の値を書き込んでいます。命令を指定するために利用している文字列リテラルが、実はテンプレート文字列になっています。これは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
と書くことで宣言されています。
そして、他のフォーマット文字列と同じように引数を番号や名前で指定できます。インラインアセンブリのテンプレートでは、引数が2回以上利用されることが多いため、これは特に便利です。より複雑なインラインアセンブリを書く場合、この機能を使うのが推奨されます。可読性が向上し、引数の順序を変えることなく命令を並べ替えることができるからです。
上記の例をさらに改善して、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); } }
In unoptimized cases (e.g. Debug
mode), replacing inout(reg) a
with inlateout(reg) a
in the above example can continue to give the expected result. However, with release
mode or other optimized cases, using inlateout(reg) a
can instead lead to the final value a = 16
, causing the assertion to fail.
というのも、最適化されている場合、コンパイラはb
とc
が同じ値だと知っているので、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
からrax
レジスタに入力します。計算結果の下位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]; // 文字列はasciiとしてebx, edx, ecxの順に保存されています。 // ebxは予約されているので、アセンブリはebxの値を維持する必要があります。 // 従ってメインのアセンブリの前後でプッシュおよびポップを行います。 // (以下は64ビットプロセッサの64ビットモードの場合。32ビットプロセッサはebxを利用します。) unsafe { asm!( "push rbx", "cpuid", "mov [rdi], ebx", "mov [rdi + 4], edx", "mov [rdi + 8], ecx", "pop rbx", // いくつかのアセンブリ命令を追加してRustのコードを単純化するために // 値を格納する配列へのポインタを利用します。 // しかし、`out("ecx") val`のような明示的なレジスタの出力とは違い、 // アセンブリの動作をより明示的にします。 // *ポインタそのもの* は後ろに書かれていても入力にすぎません。 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を読み込んでいます。この命令はeax
にサポートされている最大のcpuid
引数を書き込み、ebx
、edx
、ecx
の順にCPUベンダIDをASCIIコードとして書き込みます。
eax
は読み込まれることはありません。しかし、コンパイラがアセンブリ以前にこれらのレジスタにあった値を保存できるように、レジスタが変更されたことをコンパイラに伝える必要があります。そのために、変数名の代わりに_
を用いて出力を宣言し、出力の値が破棄されるということを示しています。
このコードはebx
がLLVMによって予約されたレジスタであるという制約を回避しています。LLVMは、自身がレジスタを完全にコントロールし、アセンブリブロックを抜ける前に元の状態を復元しなくてはならないと考えています。そのため、コンパイラがin(reg)
のような汎用レジスタクラスを満たすために使用する場合 を除いて ebx
を入力や出力として利用できません。つまり、予約されたレジスタを利用する場合に、reg
オペランドは危険なのです。入力と出力が同じレジスタを共有しているので、知らないうちに入力や出力を破壊してしまうかもしれません。
これを回避するために、rdi
を用いて出力の配列へのポインタを保管し、push
でebx
を保存し、アセンブリブロック内でebx
から読み込んで配列に書き込み、pop
でebx
を元の状態に戻しています。push
とpop
は完全な64ビットのrbx
レジスタを使って、レジスタ全体を確実に保存しています。32ビットの場合、push
とpop
においてebx
がかわりに利用されるでしょう。
アセンブリコード内部で利用するスクラッチレジスタを獲得するために、汎用レジスタクラスとともに使用することもできます。
#![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にあります。 in("rdi") arg, // 戻り値はraxにあります。 out("rax") result, // "C"の呼び出し規約で保存されていないすべてのレジスタをクロバーに指定。 clobber_abi("C"), ); result } } } }
レジスタテンプレート修飾子
テンプレート文字列に挿入されるレジスタの名前のフォーマット方法について、細かい制御が必要な場合があります。アーキテクチャのアセンブリ言語が、同じレジスタに別名を持っている場合です。典型的な例としては、レジスタの部分集合に対する"ビュー"があります(例:64ビットレジスタの下位32ビット)。
デフォルトでは、コンパイラは常に完全なレジスタサイズの名前を選択します(例:x86-64ではrax
、x86ではeax
、など)。
この挙動は、フォーマット文字列と同じように、テンプレート文字列のオペランドに修飾子を利用することで上書きできます。
#![allow(unused)] fn main() { #[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
レジスタクラスを用いて、レジスタアロケータを4つのレガシーなx86レジスタ(ax
, bx
, cx
, dx
)に制限しています。このうち最初の2バイトは独立して指定できます。
レジスタアロケータがx
をax
レジスタに割り当てることにしたと仮定しましょう。h
修飾子はそのレジスタの上位バイトのレジスタ名を出力し、l
修飾子は下位バイトのレジスタ名を出力します。したがって、このアセンブリコードはmov ah, al
に展開され、値の下位バイトを上位バイトにコピーします。
より小さなデータ型(例:u16
)をオペランドに利用し、テンプレート修飾子を使い忘れた場合、コンパイラは警告を出力し、正しい修飾子を提案してくれます。
メモリアドレスオペランド
アセンブリ命令はオペランドがメモリアドレスやメモリロケーション経由で渡される必要なこともあります。そのときは手動で、ターゲットのアーキテクチャによって指定されたメモリアドレスのシンタックスを利用しなくてはなりません。例えば、Intelのアセンブリシンタックスを使うx86/x86_64の場合、入出力を[]
で囲んで、メモリオペランドであることを示さなくてはなりません。
#![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!
ブロック中で、または複数のブロック中で2回以上利用する場合です。 - インライン化による暗黙の再利用:コンパイラは
asm!
ブロックの複数のコピーをインスタンス化する場合があります。例えば、asm!
ブロックを含む関数が複数箇所でインライン化される場合です。 - LTO(訳注:Link Time Optimizationの略)による暗黙の再利用:LTOは 他のクレート のコードを同じコード生成単位に配置するため、同じ名前のラベルを持ち込む場合があります。
そのため、インラインアセンブリコードの中では、GNUアセンブラの 数値型ローカルラベルのみ使用してください。アセンブリコード内でシンボルを定義すると、シンボル定義の重複により、アセンブラやリンカのエラーが発生する可能性があります。
さらに、x86でデフォルトのIntel構文を使用する場合、LLVMのバグによって、0
、11
、101010
といった0
と1
だけで構成されたラベルは、バイナリ値として解釈されてしまうため、使用してはいけません。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"("後方")や"f"("前方")の接尾辞が数字のラベルに追加されなくてはなりません。そうすることで、この数字の指定された方向の最も近いラベルを参照できます。
オプション
デフォルトでは、インラインアセンブリブロックは、カスタム呼び出し規約をもつ外部の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
は、アセンブリコードが観測可能な副作用を持っておらず、出力は入力のみに依存することを意味します。これにより、コンパイラオプティマイザはインラインアセンブリの呼び出し回数を減らしたり、インラインアセンブリを完全に削除したりできます。nomem
は、アセンブリコードがメモリの読み書きをしないことを意味します。デフォルトでは、インラインアセンブリはアクセス可能なメモリアドレス(例えばオペランドとして渡されたポインタや、グローバルなど)の読み書きを行うとコンパイラは仮定しています。nostack
は、アセンブリコードがスタックにデータをプッシュしないことを意味します。これにより、コンパイラはx86-64のスタックレッドゾーンなどの最適化を利用し、スタックポインタの調整を避けることができます。
これにより、コンパイラは、出力が全く必要とされていない純粋なasm!
ブロックを削除するなどして、asm!
を使ったコードをより最適化できます。
利用可能なオプションとその効果の一覧はリファレンスを参照してください。
互換性
The Rust language is evolving rapidly, and because of this certain compatibility issues can arise, despite efforts to ensure forwards-compatibility wherever possible.
生識別子
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();
}
周辺情報
この章では、プログラミングそれ自体に関係はないけれども、色々と人々の役に立つ機能やインフラについて説明していきます。例えば:
- ドキュメンテーション: Rust付属コマンド
rustdoc
を用いて、ライブラリのドキュメントを生成します。 - プレイグラウンド: あなたのドキュメンテーションにRust Playgroundを組み込めます。
ドキュメンテーション
Use cargo doc
to build documentation in target/doc
, cargo doc --open
will automatically open it in your web browser.
Use cargo test
to run all tests (including documentation tests), and cargo test --doc
to only run documentation tests.
These commands will appropriately invoke rustdoc
(and rustc
) as required.
ドキュメンテーションコメント
ドキュメンテーションコメントとはrustdoc
を使用した際にドキュメントにコンパイルされるコメントのことです。///
によって普通のコメントと区別され、ここではMarkdownを使用することができます。ドキュメンテーションコメントは大規模なプロジェクトの際に非常に有用です。
#![crate_name = "doc"]
/// あらゆる人物はここに代表されます。
pub struct Person {
/// ジュリエットがどんなに名前というものを嫌っていようと、
/// 人物には名前が必要です。
name: String,
}
impl Person {
/// 与えられた名前を持つpersonを返します。
///
/// # Examples
///
/// ```
/// // バッククォートによってRustのコードをコメント中に挟むこと
/// // もできます。`rustdoc`に--testを渡せば、テストも行えます!
/// // (訳注: pythonのdoctestと同じです。)
/// 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();
}
To run the tests, first build the code as a library, then tell rustdoc
where to find the library so it can link it into each doctest program:
$ rustc doc.rs --crate-type lib
$ rustdoc --test --extern doc="libdoc.rlib" doc.rs
Doc attributes
Below are a few examples of the most common #[doc]
attributes used with rustdoc
.
inline
Used to inline docs, instead of linking out to separate page.
#[doc(inline)]
pub use bar::Bar;
/// bar docs
pub mod bar {
/// the docs for Bar
pub struct Bar;
}
no_inline
Used to prevent linking out to separate page or anywhere.
// Example from libcore/prelude
#[doc(no_inline)]
pub use crate::mem::drop;
hidden
Using this tells rustdoc
not to include this in documentation:
// Example from the futures-rs library
#[doc(hidden)]
pub use self::async_await::*;
For documentation, rustdoc
is widely used by the community. It's what is used to generate the std library docs.
参照
- The Rust Book: Making Useful Documentation Comments
- The rustdoc Book
- The Reference: Doc comments
- RFC 1574: API Documentation Conventions
- RFC 1946: Relative links to other items from doc comments (intra-rustdoc links)
- Is there any documentation style guide for comments? (reddit)
プレイグラウンド
Rust Playgroundでは、RustのコードをWebのインターフェースを通じて実験できます。
mdbook
と組み合わせる
mdbook
では、コード例を実行・編集可能にできます。
fn main() { println!("Hello World!"); }
これにより、読者はあなたのコード例を実行するだけでなく、変更することもできます。editable
という単語をカンマで区切って、あなたのコードブロックに追加するのがキーです。
```rust,editable
//...place your code here
```
加えて、mdbook
がビルドやテストを実行するときに、あなたのコードをスキップさせたい場合は、ignore
を追加できます。
```rust,editable,ignore
//...place your code here
```
ドキュメントと組み合わせる
Rustの公式ドキュメントには、「実行(Run)」ボタンがある場合があります。クリックすると、新しいタブでRust Playgroundが開き、コード例が表示されます。この機能は、#[doc]アトリビュートのhtml_playground_url
の値で有効化できます。
#![doc(html_playground_url = "https://play.rust-lang.org/")]
//! ```
//! println!("Hello World");
//! ```