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におけるテストのすべて。
- 周辺情報 - ドキュメント、ベンチマークの方法。
Hello World
ここでは伝統的な"Hello World!"プログラムのソースを紹介します。
println!
は文字列をコンソールにプリントするための マクロ です。
バイナリファイルはrustc
と呼ばれるRustのコンパイラを用いて生成することができます。
$ rustc hello.rs
するとhello
という名前の実行可能なバイナリファイルができます。
$ ./hello
Hello World!
演習
上に書いている'Run'をクリックしてアウトプットを見てみましょう。
次に、println!
マクロをもう一行追加してアウトプットがどうなるか見てみましょう。
Hello World!
I'm a Rustacean!
コメント
あらゆるプログラムにはコメントが必要です。Rustには何種類かのコメントがあります
- 通常のコメント これはコンパイラによって完全に無視されます。
// 行末までコメントアウト
/* ブロックによって囲まれた部分をコメントアウト */
- ドキュメンテーションコメント ライブラリのドキュメンテーションとしてhtmlにパースされます。
/// このコメントの下の内容に関するドキュメントとなります
//! このコメントを含むソースのドキュメントになります
参照
フォーマットしてプリント
プリント関係の機能はstd::fmt
で定義される幾つかのマクロによって扱われます。このマクロには以下が含まれます。
format!
: フォーマットされたテキストを文字列(String)型に書き込みます。print!
:format!
と同様ですが、コンソール (io::stdout) にそのテキストを出力します。println!
:print!
: と同じですが改行が付け加えられます。eprint!
:format!
と同様ですが、標準エラー出力 (io::stderr) にそのテキストを出力します。eprintln!
:eprint!
と同じですが改行が付け加えられます。
すべて同じやり方でテキストをパースし、正しくフォーマットできるかコンパイル時にチェックします。
std::fmt
はいくつものトレイトを持ち、それによってどのようにディスプレイに表示されるかが決まります。
特に大事な形式は以下の2つです。
fmt::Debug
: は、{:?}
というマーカーを使用し、デバッギング目的に使われます。fmt::Display
: は{}
というマーカーを使用し、より美しく、ユーザフレンドリーに表示します。
この例で用いられている型は、標準ライブラリに含まれているため、ここではfmt::Display
を使用しています。カスタム型をテキストとして表示する場合は、さらに手順が必要です。
fmt::Display
トレイトを実装すると、自動的にToString
トレイトが実装されます。これによりString
型への型変換ができるようになります。
46行目 の#[allow(dead_code)]
は、直後のモジュールにのみ適用されるアトリビュートです。
演習
- 上の例を実行した際に生じるエラーを修復しましょう。
Structure
構造体をフォーマットする行をアンコメントしてみましょう。println!
マクロを追加し、表示される小数部の桁数を調整してPi is roughly 3.142
という文字列を出力しましょう。 ただし、円周率の値はlet pi = 3.141592
を使ってください。(ヒント: 小数部の桁数を調整する方法については、std::fmt
をチェックする必要があるかもしれません。)
参照
std::fmt
, マクロ, 構造体,
トレイト, dead_code
デバッグ
std::fmt
のフォーマット用トレイト
を使用したい型は、プリント可能である用に実装されている必要があります。std
ライブラリの型のように自動でプリント可能なものもありますが、他はすべて 手動で実装する必要があります。
fmt::Debug
というトレイト
はこれを簡略化します。 すべての 型はfmt::Debug
の実装をderive
、(すなわち自動で作成)することができるためです。
fmt::Display
の場合はやはり手動で実装しなくてはなりません。
std
ライブラリの型の場合は、自動的に{:?}
によりプリント可能になっています。
fmt::Debug
は確実にプリント可能にしてくれるのですが、一方である種の美しさを犠牲にしています。
Rustは{:#?}
による「見栄えの良いプリント」も提供します。
手動でfmt::Display
を実装することでプリント結果を思い通りにできます。
参照
アトリビュート, derive
, std::fmt
,
構造体
ディスプレイ
fmt::Debug
はコンパクトでクリーンであるようには見えませんね。大抵の場合は、アウトプットの見た目をカスタマイズしたほうが好ましいでしょう。これは{}
を使用するfmt::Display
を手動で実装することで可能です。
fmt::Display
はfmt::Debug
より綺麗かもしれませんが、std
ライブラリの場合は問題が生じます。曖昧な(ambiguous)タイプはどのように表示すれば良いでしょう?
例えば、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
を実装することができます。
fmt::Display
は実装されていますが、fmt::Binary
はされていないので使用できません。
std::fmt
はそのようなトレイトが数多くあり、それぞれに独自の実装が必要です。詳しくはstd::fmt
を参照してください。
演習
上記の例のアウトプットを確認し、Point2D
構造体を参考として、複素数を格納するためのComplex
構造体を定義しましょう。うまく行けば以下のように出力されるはずです。
Display: 3.3 + 7.2i
Debug: Complex { real: 3.3, imag: 7.2 }
参照
derive
, std::fmt
, マクロ, struct
,
trait
, use
テストケース: リスト
構造体のそれぞれの要素を別々に扱うfmt::Display
を実装するのはトリッキーです。というのも、それぞれのwrite!
が別々のfmt::Result
を生成するためです。適切に処理するためには すべての resultに対して処理を書かなくてはなりません。このような場合は?
演算子を使用するのが適当です。
以下のように?
をwrite!
に対して使用します。
// Try `write!` to see if it errors. If it errors, return
// the error. Otherwise continue.
// `write!`を実行し、エラーが生じた場合はerrorを返す。そうでなければ実行を継続する。
write!(f, "{}", value)?;
?
を使用できれば、Vec
用のfmt::Display
はより簡単に実装できます。
演習
上記のプログラムを変更して、ベクタの各要素のインデックスも表示するようにしてみましょう。変更後の出力は次のようになります。
[0: 1, 1: 2, 2: 3]
参照
for
, ref
, Result
, 構造体,
?
, vec!
フォーマット
これまで、文字列がどのようにフォーマットされるかは フォーマット文字列 によって決まるということを見てきました 。
format!("{}", foo)
->"3735928559"
format!("0x{:X}", foo)
->"0xDEADBEEF"
"0xDEADBEEF"
format!("0o{:o}", foo)
->"0o33653337357"
ここでは(foo
)という単一の変数がX
、o
、 指定なし 、という様々な 引数タイプ (argument type)に応じてフォーマットされています。
フォーマットの機能はそれぞれの引数タイプごとに個別のトレイトを用いて実装されています。
最も一般的なトレイトはDisplay
で、これは引数タイプが未指定(たとえば{}
)の時に呼び出されます。
フォーマット用トレイトの全リスト、及び引数の型はこちらから、引数の型についてはstd::fmt
のドキュメンテーションから参照できます。
演習
上にあるソースコード中のColor
という構造体のためのfmt::Display
トレイトの実装を追加しましょう。アウトプットは以下のように表示されるはずです。
RGB (128, 255, 90) 0x80FF5A
RGB (0, 3, 254) 0x0003FE
RGB (0, 0, 0) 0x000000
詰まったら以下の2つがヒントになります。
参照
基本データ型
Rustは様々な基本データ型(primitives
)の使用をサポートしています。以下がその例です。
スカラー型
- 符号付き整数:
i8
,i16
,i32
,i64
,i128
,isize
(ポインタのサイズ) - 符号無し整数:
u8
,u16
,u32
,u64
,u128
,usize
(ポインタのサイズ) - 浮動小数点数:
f32
,f64
char
:'a'
,'α'
,'∞'
などのUnicodeのスカラー値bool
:true
またはfalse
- ユニット型:
()
が唯一の値
ユニット型はその値がタプルですが、複合型とはみなされません。内部に複数の値を含んでいるわけではないからです。
複合型
- 配列: e.g.
[1, 2, 3]
など - タプル: e.g. (1, true)
変数は常に 型指定(type annotate
)可能 です。数値型の場合はさらにサフィックスでの指定が可能です。指定しない場合デフォルトになります。例えば整数はi32
が、浮動小数点はf64
がデフォルトです。また、Rustは文脈から型を推定することもできます。
参照
std
ライブラリ, mut
, inference
, shadowing
リテラルとオペレータ
整数1
、浮動小数点1.2
、文字(char
)'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などの言語のものとほぼ同じです。
タプル
タプルは異なる型の値の集合です。括弧()
を用いて生成します。タプル自体がそのメンバに対する型シグネチャを保持していますので、明示すると(T1, T2, ...)
のようになります。タプルは大きさに制限がありませんので、関数が複数の値を返したい時に使われます。
演習
-
復習 : 上にある
Matrix
という構造体に、fmt::Display
トレイトを追加しましょう。デバッグフォーマット{:?}
ではなくディスプレイフォーマット{}
でプリントすることができるようになるはずです。( 1.1 1.2 ) ( 2.1 2.2 )
必要に応じてprint displayのページに戻る必要があるかもしれません。
-
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]
という型シグネチャを持ちます。
カスタム型
Rustでのカスタムデータ型の作成は主に以下の2つのキーワードを介して行われます。
struct
: 構造体を定義するenum
: 列挙型を定義する
const
、あるいはstatic
というキーワードによって定数を定義することもできます。
構造体
struct
というキーワードを用いて作成できる構造体には3種類あります。
- タプル。(すなわちタプルに名前が付いたようなもの)
- クラシックなC言語スタイルの構造体。
- ユニット。これはフィールドを持たず、ジェネリック型を扱う際に有効です。
演習
Rectangle
の面積を計算するrect_area
関数を追加してください。ネストしたデストラクトを使ってみましょう。Point
とf32
を引数とした時にRectangle
を返すsquare
関数を追加してください。Rectangle
の左下の点がPoint
になり、f32
がRectangle
の幅と高さになります。
See also
列挙型
列挙型(enum
)はいくつかの異なる要素型の中から1つを選ぶような場合に使用します。構造体(struct
)の定義を満たすものならば何でもenum
の要素型として使用できます。
型エイリアス
型エイリアスを用いると、列挙型の要素型を別名で参照できます。これは列挙型の名前があまりに長かったり、あまりに一般的だったりで改名したい場合に役立ちます。
このやり方がもっともよく見られるのは、impl
ブロックでSelf
という別名を使用する場合です。
列挙型や型エイリアスについて詳しく学びたい人は、この機能が安定してRustに取り込まれた後にstabilization reportを読んでください。
参照
マッチ(match
), 関数(fn
), 文字列(String
), "Type alias enum variants" RFC
use
use
を使用すれば変数のスコープを絶対名で指定する必要がなくなる。
参照
C言語ライクな列挙型
列挙型はC言語の列挙型のような使い方をする事もできます。
参照
テストケース: 連結リスト
enum
を使用が適切なパターンのひとつに、連結リスト(linked-list
)を作成する場合があります。
参照
定数
Rustには2種類の定数があり、いずれもグローバルスコープを含む任意のスコープで宣言することができます。また、いずれも型を明示しなくてはなりません。
const
: 不変の値(通常はこちらを使用する)static
: スタティックなライフタイムを持つミュータブル(mut
)な値 The static lifetime is inferred and does not have to be specified. Accessing or modifying a mutable static variable isunsafe
.
参照
const
及び static
の RFC,
'static
ライフタイム
変数束縛
Rustは静的(static
)な型付けゆえに型安全です。変数束縛は宣言時に型を指定できます。とはいえたいていの場合は、コンパイラは変数の型をコンテキストから推測することができますので、型指定の負担を大幅に軽減できます。
値(リテラルなど)はlet
を用いて変数に束縛することができます。
ミュータビリティ
変数はデフォルトでイミュータブル(変更不可能)ですがmut
構文を使用することでミュータブルになります。
コンパイラはミュータビリティに関するエラーの詳細を出してくれます。
スコープとシャドーイング
変数はスコープを持つため、 ブロック の中に閉じ込められています。ブロックとは{}
で囲まれた領域のことです。
同様に、変数のシャドーイングも可能です。
宣言
変数の宣言だけを行っておき、初期化(定義)をのちに行うことも可能です。 しかし、最後まで初期化されない変数が生じる可能性があるため、ふつうは同時に行われます。
未初期化の変数があると予期せぬ動作をする場合があるため、コンパイラは変数を初期化してから使用するよう強制します。
値の凍結
データを同じ名前のイミュータブルな変数に束縛しなおすと、データは凍結されます。凍結したデータは、イミュータブルな束縛がスコープ外になるまで変更できません。
型
Rustには、プリミティブ型やユーザ定義型を定義したり変換したりする様々な方法があります。 この章は以下の内容を扱います:
型キャスト
Rustはプリミティブ型における強制的な型変換を暗黙に行うことはありません。しかし明示的に行うこと(casting
)は可能です。その場合as
キーワードを使用します。
整数型から整数型へ型変換する場合、C言語で可能なケースの場合はC言語と同じです。 C言語で未定義の場合の挙動も、Rustでは完全に定義されています。
リテラル
数値型リテラルはサフィックスにより型を指定することが可能です。例えば、42
というリテラルに対してi32
型を指定するには42i32
とします。
サフィックスを指定しない数値型リテラルの場合、その型はどのように使用されるかに依存して決められます。デフォルトでは整数型の場合i32
が、浮動小数点型はf64
を使用します。
上のコードには現時点では解説していない考えがいくつか使用されています。気になる方のために簡単に説明をしておきましょう。
std::mem::size_of_val
は関数ですが、 絶対パス(full path
) で呼び出されています。ソースコードは論理的に区切られた モジュール と呼ばれるものにわけられることができます。今回の場合はsize_of_val
関数はmem
モジュール内で定義されており、mem
モジュールはstd
クレート 内で定義されています。より詳しくはクレート(crates
)を参照してください。
型推論
Rustの型推論エンジンはなかなか賢くできています。初期化の際に評価値の型をチェックするだけでなく、その後にどのような使われ方をしているかを見て推論します。以下がその例です。
このように、変数の型アノテーションは必要ありません。これでコンパイラもプログラマもハッピーですね!
エイリアス
type
文を使用することで既存の型に新しい名前(alias
)を付けることができます。その場合、名前はUpperCamelCase
でなくてはなりません。さもなくばコンパイラがエラーを出します。唯一の例外はusize
やf32
のようなプリミティブ型です。
このようにエイリアスを付ける一番の理由はボイラープレートを減らすことです。例えば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
への型変換は簡単です。
自作の型に対しても、型変換を定義すれば同じように行えます。
Into
Into
トレイトは、単にFrom
トレイトの逆の働きをします。もし自作の型にFrom
トレイトが実装されていたら、Into
は必要に応じてそれを呼び出します。
Into
トレイトを使用すると、ほとんどの場合、コンパイラが型を決定することができないため、変換する型を指定する必要があります。しかし、この機能を無料で得られることを考えれば、これは小さなトレードオフです。
TryFrom
およびTryInto
From
およびInto
と同様に、TryFrom
およびTryInto
も型変換を行うジェネリックなトレイトです。From
/Into
と異なり、TryFrom
/TryInto
トレイトは失敗する可能性のある型変換に用いられるので、Result
を返します。
Stringとの型変換
Stringへの型変換
任意の型をString
に変換するのは簡単で、その型にToString
トレイトを実装するだけです。これを直接実装するよりも、fmt::Display
トレイトを実装するのがよいでしょう。そうすることで自動的にToString
が提供されるだけでなく、print!
の章で説明したように、その型を表示できるようにもなります。
Stringの解析
文字列からの型変換において、数値への型変換はよく行われるものの一つです。これを行うイディオムはparse
関数を使用することですが、このときに型を推論できるようにするか、もしくは turbofish構文を使用して型を指定するかのいずれかを行います。以下の例では、どちらの方法も紹介しています。
parse
関数は、指定された型にFromStr
トレイトが実装されていれば、文字列をその型に変換します。このトレイトは標準ライブラリの多くの型に対して実装されています。ユーザー定義の型でこの機能を利用するには、その型に対してFromStr
トレイトを実装するだけです。
式
Rustのプログラムは(ほとんどの場合)文(statement
)の連続でできています
宣言文にはいくつかの種類があります。最も一般的なのは変数の束縛(variable binding
)と;
付きの式(expression
)です
コードブロックも式の一種です。よってブロックを丸ごと値として扱うことができます。その場合ブロック内の最後の式が場所を表す式(例えばローカル変数)に代入されます。ただし、ブロック内の最後の式が;
で終わる場合は返り値は()
になります。
条件分岐
処理の流れをコントロールすることはあらゆるプログラミング言語において重要な要素です。
if
/else
, for
等です。Rustの文法を見ていきましょう。
if/else
if-else
を用いた条件分岐は他の言語に似ています。多くの言語では条件式の中を括弧でくくる必要がありますが、Rustではその必要はありません。条件式の直後にはブロックが続きます。if-else
は式の一種で、いずれの分岐先でも返り値の型は同一でなくてはなりません。
loop
Rustにはloop
というキーワードが存在します。これは無限ループを作成するのに使用します。
訳注:
while True
と同じですが、ループのたびに条件を確認しないため、若干高速になります。
ループから抜けだす時はbreak
, 即座に次のループに移るときはcontinue
が使用できます。
ネストとラベル
ネストしたループを回している時に外側のループをbreak
またはcontinue
したい場合があります。こういった場合にはlabel
を用いてループにラベルを貼り、break
/continue
にそのラベルを渡します。
loopが返す値
loop
の用途のひとつに「成功するまである処理を再試行する」ことがあります。もしその処理が値を返すならば、それをコードの他の部分に渡す必要があるでしょう。break
の後に値を置くと、それがloop
式の値として返されます。
while
while
キーワードは条件が真である限り実行され続けるループのために使用します。
悪名高いFizzBuzz問題をwhile
を用いて解いてみましょう。
forループ
for と range
for in
文を用いることで、イテレータ(Iterator
)のそれぞれの要素に対して処理をすることが可能です。イテレータを作る最も単純な方法はa..b
のような書き方をすることです。これは「a
」から「b
のひとつ前」までの要素を順に産出(yield
)するというものです。
ではfor
とwhile
を用いてFizzBuzzを書いてみましょう。
上記の代わりにa..=b
を用いると、両端の値を含む範囲を指定できます。上記の例は次のように書けます。
forとイテレータ
for in
構文はIterator
とさまざまな方法でやり取りできます。Iteratorトレイトの章で説明したように、デフォルトではfor
ループにおいてinto_iter
関数がコレクションに対して適用されます。しかし、コレクションをイテレータに変換する方法はこれだけではありません。
into_iter
、iter
、iter_mut
はいずれもコレクションのイテレータへの変換を行いますが、データの「見せ方」の違いにより、そのやり方はそれぞれ異なります。
iter
- この関数は、各周回においてコレクションの要素を借用します。よってコレクションには手を加えないので、ループの実行後もコレクションを再利用できます。
into_iter
- この関数はコレクションからデータを取り出すので、各周回において要素のデータそのものが提供されます。データを取り出してしまうと、データはループ内に「移動」してしまうので、ループ実行後にコレクションを再利用することはできません。
iter_mut
- この関数はコレクションの各要素をミュータブル(変更可能)で借用するので、コレクションの要素をその場で変更できます。
上記に示した3つのコードにおいて、match
の選択肢の型の違いに注意してください。ここがそれぞれの方法の違いを生む鍵になっています。型が異なれば、当然ながらそれに対して行える処理も変わります。
参照
match
Rustはmatch
を用いて、C言語におけるswitch
のようなパターンマッチングを行うことができます。
マッチする最初のアームが評価され、取りうるすべての値はカバーされていなければなりません。
デストラクト
match
は値をさまざまなやり方でデストラクトすることができます。
タプル
以下のように、タプルはmatch
を用いてデストラクトすることができます。
参照
配列とスライス
タプル同様、配列とスライスも以下のようにデストラクトできます:
参照
列挙型
列挙型(enum
)も似たやり方でデストラクトすることができます。
参照
ポインタとref
Rustのポインタは、C/C++のポインタとは異なる概念なので、デストラクトとデリファレンスを同じようなやり方で扱うことはできない
- デリファレンスには
*
を用いる。 - デストラクトには
&
,ref
,ref mut
を用いる。
See also:
構造体
以下のようにして、構造体(struct
)も同様にデストラクトすることができる。
参照
ガード
match
内の条件文をフィルタリングするために、 ガード(guard
) を使用することができます。
コンパイラは、match式ですべてのパターンがカバーされているかどうかを調べるときに、 ガード条件を考慮しない点に注意してください。
参照
バインディング
いくつかの変数をまとめてマッチ対象とした場合、そのうちの一つを分岐先で使用することはそのままでは不可能です。match
内では@
マークを使用して変数をバインディングすることができます。
Option
のような、enum
の値をデストラクトするためにも、バインディングを利用できます。
参照
if let
列挙型をマッチさせるとき、場合によってはmatch
を使用すると不自然な書き方になってしまう場合があります。例えば...
この場合はif let
を用いたほうが美しく、失敗時の処理も柔軟に行うことができます。
同じように、if let
を列挙型の値にマッチさせるのに利用できます。
if let
を利用する別の利点は、パラメータ化されていない列挙型の値をマッチさせられることです。
これは、列挙型がPartialEq
を実装もderiveもしていない場合でも同様です。
PartialEq
がない場合には、if Foo::Bar == a
はコンパイルできません。
列挙型のインスタンスは比較できませんが、if let
を使えば動作します。
次の例をif let
を利用して修正するのにチャレンジしてみましょう。
参照
let-else
🛈 stable since: rust 1.65
🛈 you can target specific edition by compiling like this
rustc --edition=2021 main.rs
With let
-else
, a refutable pattern can match and bind variables
in the surrounding scope like a normal let
, or else diverge (e.g. break
,
return
, panic!
) when the pattern doesn't match.
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"));
}
The scope of name bindings is the main thing that makes this different from
match
or if let
-else
expressions. You could previously approximate these
patterns with an unfortunate bit of repetition and an outer let
:
See also:
option, match, if let and the let-else RFC.
while let
if let
と同様に、while let
も不格好なmatch
処理を多少マシにしてくれます。例えば、以下のi
をインクリメントする処理を見てください。
while let
の使用によってベターになります。
参照
関数
関数はfn
キーワードを用いて定義することができます。引数は変数と同様に型を指定する必要があり、もし関数が値を返すならば->
の後にその型も指定する必要があります。
関数内の最後の式が返り値となります。関数の途中で値を返したい場合はreturn
文を使用します。loop
の最中やif
文の中からも値を返すことができます。
では、もう一度FizzBuzz問題を解く関数を書いてみましょう!
関連関数とメソッド
関数には特定の型に紐づいたものがあります。これには関連関数とメソッドの2つの形式があります。 メソッドは特定のインスタンスに関連付けて呼ばれる関数であるのに対し、関連関数は型全体に対して定義される関数です。
クロージャ
Rustにおけるクロージャは、その外側の環境を捕捉した関数のことです。例えば、次のコードは変数x
を捕捉したクロージャです。
|val| val + x
クロージャの構文や機能は、その場限りの用途で何かを作るのに便利です。クロージャの呼び出しは関数の呼び出しと全く同じです。しかし、入力の型と戻り値の型は推論させることができますが、入力変数の名前は必ず指定しなくてはなりません。
クロージャの他の特徴を以下に示します。
- 入力変数を囲むのに、
()
の代わりに||
を用います。 - 本体が単一の式の場合は、本体の区切り文字(
{}
)を省略できます。(それ以外の場合は必須です) - 外側の環境にある変数を捕捉することができます。
要素の捕捉
クロージャはとてもフレキシブルに動作するように出来ています。クロージャにおいて型アノテーションをする必要が無いのは前述の仕組みのためですが、この仕組みのおかげでユースケースに応じて参照を取得したり値そのものを取得したりといった動作が可能になります。 クロージャは外側の環境にある要素を、以下の形で取得することができます。
- リファレンス:
&T
- ミュータブルなリファレンス:
&mut T
- 値そのもの:
T
クロージャは出来る限りリファレンスを取得しようとし、その他2つは必要なときのみ取得します。
バーティカルパイプ(訳注:縦線記号||
)の前にmove
を使用することで、キャプチャする変数の所有権を取ることをクロージャに強制します。
参照
Box
and std::mem::drop
捕捉時の型推論
Rustはたいていの場合、型アノテーションなしでも変数を捕捉する方法を臨機応変に選択してくれますが、関数を書く場合にはこの曖昧さは許されません。
引数のパラメータとしてクロージャを取る場合、そのクロージャの完全な型はいくつかのtraits
の中の1つを使って明示されなければなりません。
どれが使われるかは、捕捉された値でクロージャが何をするかによって決まります。
制限の少ない順に並べると、下記の通りです。
Fn
: 参照(&T
)によって捕捉するクロージャFnMut
: ミュータブルな参照(&mut T
)によって捕捉するクロージャFnOnce
: 値(T
)によって捕捉するクロージャ
変数ごとに、コンパイラは可能な限り制約の少ない方法でその変数を捕捉します。
例えば、FnOnce
というアノテーションの付けられたパラメータを考えてみましょう。
これはそのクロージャが&T
、&mut T
もしくはT
の どれか で捕捉することを指定するものですが、コンパイラは捕捉した変数がそのクロージャの中でどのように使用されるかに基づき、最終的に捕捉する方法を選択することになります。
これは、もし移動が可能であれば、いずれの種類の借用であっても同様に可能だからです。
その逆は正しくないことに注意してください。パラメータがFn
としてアノテーションされている場合、変数を&mut T
やT
で捕捉することは許可されません。
しかし&T
は許可されます。
以下の例では、Fn
、FnMut
、およびFnOnce
を入れ替えて、何が起こるのかを見てみましょう。
参照
std::mem::drop
, Fn
, FnMut
, Generics, where and FnOnce
クロージャを受け取る関数
クロージャが周辺の環境から変数を取得するやり方は非常に明瞭です。何か注意すべき点はあるのでしょうか? もちろんです。関数内でクロージャを使う場合、[ジェネリック]型を使用する必要があります。詳しく見ていきましょう。
クロージャが定義されると、コンパイラは裏側で、無名の構造体を作り、そこにクロージャによって使用される外側の変数を入れます。同時にFn
、FnMut
、FnOnce
という名のトレイトのいずれか一つを介してこの構造体に関数としての機能を実装し、実際に呼び出されるまで待ちます。
この無名構造体は型が未指定(unknown
)なため、関数を実行する際にはジェネリクスが必要とされます。とはいえ、<T>
で指定するだけでは、まだ曖昧です。(訳注: &self
、&mut self
、self
のいずれをとるのかがわからないため)そのため、Fn
、FnMut
、FnOnce
のいずれか一つを実装することで対応しています。
参照
A thorough analysis, Fn
, FnMut
,
and FnOnce
関数を受け取る関数
これまで、クロージャを引数として渡せることを見てきました。すると次の疑問が浮かんできます
「クロージャではない普通の関数を引数として渡すことは可能なのだろうか?」
可能です!もしパラメータとしてクロージャを取る関数を定義すれば、そのクロージャのトレイト境界を満たす任意の関数をパラメータとして渡すことができます。
クロージャによる変数の捕捉がどのように行われているかを詳しく見たいときはFn
、FnMut
、FnOnce
を参照してください。
参照
クロージャを返す関数
クロージャを引数のパラメータとして用いることができるのと同様に、クロージャを戻り値として返すことも可能です。しかし無名のクロージャの型はその定義上、不明であるため、クロージャを返すためにはimpl Trait
を使用する必要があります。
クロージャを返すために有効なトレイトは下記の通りです。
Fn
FnMut
FnOnce
更に、move
というキーワードを使用し、全ての捕捉が値でおこなわれることを明示しなければなりません。
これは、関数を抜けると同時に参照による捕捉がドロップされ、無効な参照がクロージャに残ってしまうのを防ぐためです。
参照
Fn
, FnMut
, ジェネリクス, impl Trait.
std
における使用例
この節ではstd
ライブラリを用いて、クロージャの利用例を幾つかお見せします。
Iterator::any
iterator::any
は、イテレータ内に一つでも条件を満たす要素があれば、true
を返し、さもなくばfalse
を返すイテレータです。以下がそのシグネチャです
pub trait Iterator {
// The type being iterated over.
// イテレートされる値の型
type Item;
// `any` takes `&mut self` meaning the caller may be borrowed
// and modified, but not consumed.
// `any`は`&mut self`を取るため、イテレータを呼び出した値を借用し
// 変更しますが、消費し尽くすことはありません。
fn any<F>(&mut self, f: F) -> bool where
// `FnMut` meaning any captured variable may at most be
// modified, not consumed. `Self::Item` states it takes
// arguments to the closure by value.
// `FnMut`はクロージャによって捕捉される変数が変更される
// 事はあっても消費されることはないということを示します。
// `Self::Item`はクロージャが変数を値として取ることを示します。
F: FnMut(Self::Item) -> bool;
}
参照
Searching through iterators
Iterator::find
はイテレータを辿る関数で、条件を満たす最初の値を探します。もし条件を満たす値がなければNone
を返します。型シグネチャは以下のようになります。
pub trait Iterator {
// The type being iterated over.
// イテレートされる値の型
type Item;
// `find` takes `&mut self` meaning the caller may be borrowed
// and modified, but not consumed.
// `find`は`&mut self`を取るため、イテレータを呼び出した値を借用し
// 変更しますが、消費し尽くすことはありません。
fn find<P>(&mut self, predicate: P) -> Option<Self::Item> where
// `FnMut` meaning any captured variable may at most be
// modified, not consumed. `&Self::Item` states it takes
// arguments to the closure by reference.
// `FnMut`はクロージャによって捕捉される変数が変更される
// 事はあっても消費されることはないということを示します。
// `&Self::Item`はクロージャが変数を参照として取ることを示します。
P: FnMut(&Self::Item) -> bool;
}
Iterator::find
は要素への参照を返します。
要素の インデックス を使用したい場合、Iterator::position
を使用してください。
参照
std::iter::Iterator::rposition
高階関数
Rustには高階関数(Higher Order Functions, HOF
)を扱う機能が備わっています。
オプション型 と イテレータには高階関数が使用されています。
発散する関数
発散する関数 (Diverging functions) は決してリターンしない関数です。こうした関数は !
を使って、空の型であることが示されます。
他の全ての型と異なり、この型はインスタンス化できません。
この型が持ちうる全ての値の集合は空です。
この型は()
型とは異なることに注意してください。
()
型は値をただ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!");
}
これは抽象的な概念に見えるかもしれませんが、実際のところはとても実用的で、便利なことも多いのです。
この型の主な利点は、他のどのような型にもキャストできることです。
そのため、例えばmatch
の分岐の中のような正確な型が要求される場所でも使用できます。
fn main() {
fn sum_odd_numbers(up_to: u32) -> u32 {
let mut acc = 0;
for i in 0..up_to {
// Notice that the return type of this match expression must be u32
// because of the type of the "addition" variable.
// 変数"addition"の型がu32であるため、
// このmatch式はu32をリターンしなければならないことに注意。
let addition: u32 = match i%2 == 1 {
// The "i" variable is of type u32, which is perfectly fine.
// 変数"i"はu32型であるため、全く問題ない。
true => i,
// On the other hand, the "continue" expression does not return
// u32, but it is still fine, because it never returns and therefore
// does not violate the type requirements of the match expression.
// 一方、"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
で修飾することでパブリックな属性にすることができます。パブリックな属性のみがモジュールの外のスコープからアクセスすることができるようになります。
構造体の場合
構造体はそれ自身に加え、フィールドごとにもパブリック・プライベートを設定することができます。デフォルトではプライベートですが、pub
宣言をすることで、フィールドをパブリックにすることができます。これは、構造体がモジュールの外から参照される時に限り意味のあるもので、情報の隠蔽(カプセル化)を達成するための機能です。
参照
use
宣言
use
宣言をすることで、要素の絶対パスを新しい名前にバインドすることができ、より簡潔な記述が可能になります。例えば以下のように使えます。
as
キーワードを使用することで、インポートを別名にバインドすることができます。
super
と self
super
及びself
キーワードは、要素にアクセスする際に、曖昧さをなくし、不必要なハードコーディングを避けるために使用できます。
ファイルの階層構造
モジュールはファイル・ディレクトリ間の階層構造と対応関係にあります。モジュールにお互いがどのように見えているか、以下の様なファイルを例に詳しく見ていきましょう。
$ tree .
.
├── my
│ ├── inaccessible.rs
│ └── nested.rs
├── my.rs
└── split.rs
split.rs
は以下のようになります:
// This declaration will look for a file named `my.rs` and will
// insert its contents inside a module named `my` under this scope
// このように宣言すると、`my.rs`という名のファイルを探し、
// その内容をこのファイル中で`my`という名から使用することができるようにします。
mod my;
fn function() {
println!("called `function()`");
}
fn main() {
my::function();
function();
my::indirect_access();
my::nested::function();
}
my.rs
は以下のようになります:
// Similarly `mod inaccessible` and `mod nested` will locate the `nested.rs`
// and `inaccessible.rs` files and insert them here under their respective
// modules
// 同様に`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
を渡すことでオーバーライドできます。
ライブラリ
ではライブラリを作成し、それを別のクレートにリンクする方法を見ていきましょう。
In 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; // May be required for Rust 2015 edition or earlier
// Rust 2015以前で必要
fn main() {
rary::public_function();
// Error! `private_function` is private
// エラー!`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:
# library.rlibがコンパイルされたライブラリのパスで、
# 同じディレクトリにあるものとする:
$ rustc executable.rs --extern rary=library.rlib && ./executable
called rary's `public_function()`
called rary's `indirect_access()`, that
> called rary's `private_function()`
Cargo
cargo
はRustの公式パッケージ管理ツールです。とても便利な機能が多くあり、コードの品質や開発速度の向上に役立ちます。以下はその例です。
- 依存関係の管理とcrates.io(Rustの公式パッケージレジストリ)とのインテグレーション
- ユニットテスト
- ベンチマーク
この章では、簡単な基本機能を説明します。包括的なドキュメントはThe Cargo Bookを参照してください。
依存関係
ほとんどのプログラムはライブラリに依存関係を持ちます。もし依存関係を手動で管理したことがあれば、それがどれだけ苦痛であるか分かるでしょう。幸運なことに、Rustのエコシステムにはcargo
が標準装備されています!cargo
によってプロジェクトの依存関係を管理することができます。
Rustのプロジェクトを新しく作るには下記のようにします。
# A binary
# バイナリ
cargo new foo
# A library
# ライブラリ
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
# 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 3 tests
test test_bar ... ok
test test_baz ... ok
test test_foo_bar ... ok
test test_foo ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
パターンにマッチする名前のテストを実行することもできます。
$ cargo test test_foo
$ cargo test test_foo
Compiling blah v0.1.0 (file:///nobackup/blah)
Finished dev [unoptimized + debuginfo] target(s) in 0.35 secs
Running target/debug/deps/blah-d3b32b97275ec472
running 2 tests
test test_foo ... ok
test test_foo_bar ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out
注意:Cargoは複数のテストを並列で実行することがありますので、それらが互いに競合しないようにしてください。
並行性が問題を引き起こす一例として、以下のように、2つのテストが1つのファイルに出力するケースがあります。
以下のような結果を得ようと意図しています。
$ cat ferris.txt
Ferris
Ferris
Ferris
Ferris
Ferris
Corro
Corro
Corro
Corro
Corro
しかし、実際にferris.txt
に出力されるのは、以下の通りです。
$ cargo test test_foo
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を参照してください。
アトリビュート
アトリビュートはモジュール、クレート、要素に対するメタデータです。以下がその使用目的です。
- コンパイル時の条件分岐
- クレート名、バージョン、種類(バイナリか、ライブラリか)の設定
- リントの無効化
- コンパイラ付属の機能(マクロ、グロブ、インポートなど)の使用
- 外部ライブラリへのリンク
- ユニットテスト用の関数を明示
- ベンチマーク用の関数を明示
アトリビュートがクレート全体に適用される場合、#![crate_attribute]
という書き方になります。モジュールないしは要素に適用される場合は#[item_attribute]
になります。(!
がないことに注目)
アトリビュートは以下の様な書き方で引数を取ることができます。
#[attribute = "value"]
#[attribute(key = "value")]
#[attribute(value)]
アトリビュートは複数の値を取ることができ、複数の行に分割することもできます。
#[attribute(value, value2)]
#[attribute(value, value2, value3,
value4, value5)]
dead_code
コンパイラはdead_code
と呼ばれるリント機能を持つため、使用されていない関数が存在するときに警告を出します。 アトリビュート によってこの機能を無効化することができます。
実際のコード中では、使用されていないコードが有る場合はそれを除外するべきです。この文書中では随所でアトリビュートによって警告を抑制していますが、それはあくまでインタラクティブな例を皆さんに提供するためです。
クレート
crate_type
アトリビュートは、そのクレートがライブラリ、バイナリのいずれにコンパイルされるべきかをコンパイラに伝えるために使用します。ライブラリの場合は、どのタイプのライブラリであるかも伝えることができます。crate_name
はクレートの名前を決定するのに使用します。
しかし、crate_type
アトリビュートもcrate_name
アトリビュートも、RustのパッケージマネージャCargoを利用している場合は何の影響もないと知っておくことは重要です。Cargoは大半のRustプロジェクトで利用されており、実世界でのcrate_type
とcrate_name
の利用は比較的限られています。
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式の中のすべてのブロックが有効でなくてはなりません。
参照
参照(reference
), cfg!
, マクロ.
条件の追加
target_os
のように、いくつかの条件分岐はrustc
が暗黙のうちに提供しています。条件を独自に追加する場合には--cfg
フラグを用いてrustc
に伝える必要があります。
独自のcfg
フラグを用いない場合、何が起きるかやってみてください。
cfg
フラグがある場合:
$ rustc --cfg some_condition custom.rs && ./custom
condition met!
ジェネリクス
ジェネリクスとは、型と関数の機能をより汎用的に使えるようにするための機能です。これはあらゆる局面でコードの重複を避けるために非常に役立ちますが、多少構文が複雑になります。すなわち、ジェネリック型を使いこなすには、どのようなジェネリック型がきちんと機能するかに細心の注意を払う必要があります。 最もシンプルで一般的なジェネリクスの利用法は型パラメータです。
ジェネリック型の型パラメータにはかぎ括弧(angle brackets
)とアッパーキャメルケース(camel case
)が使われます。: <Aaa, Bbb, ...>
ジェネリックな型パラメータはたいていの場合<T>
で示されます。Rustの場合、「ジェネリクス」には「1つ以上のジェネリックな型パラメータ<T>
を受け付けるもの」という意味もあります。ジェネリックな型パラメータを指定された場合、それは必ずジェネリック型になり、そうでなければ必ず非ジェネリック型、すなわち具象型(concrete
)になります。
例として、あらゆる型の引数T
をとる ジェネリック関数 foo
を定義すると:
fn foo<T>(arg: T) { ... }
となります。T
はジェネリックな型パラメータに指定されているので、この場所で(arg: T)
のように使用するとジェネリック型として扱われます。これはT
という構造体がそれ以前に定義されていても同様です。
では、手を動かしながらジェネリック型の構文を体験していきましょう。
参照
関数
「型T
はその前に<T>
があるとジェネリック型になる」というルールは関数に対しても当てはまります。
ジェネリック関数を使用する際、以下の様な場合には型パラメータを明示する必要があります。
- 返り値がジェネリック型である場合。
- コンパイラが型パラメータを推論するのに十分な情報がない場合
型パラメータを明示したうえでの関数呼び出しの構文はfun::<A, B, ...>()
のようになります。
参照
メソッド
関数と同様、impl
でメソッドを実装する際にもジェネリック型特有の記法が必要です。
参照
ジェネリックトレイト
もちろんトレイトもジェネリクスを活用することができます。ここではDrop
トレイトをジェネリックメソッドとして再実装し、自身と引数として受け取った値の両方をdrop
するようなメソッドにします。
参照
Drop
, 構造体(struct
), トレイト(trait
)
ジェネリック境界
ジェネリックプログラミングをしていると、型パラメータが特定の機能を持っていることを規定するため、トレイトに境界(bound
)を設ける必要があることがよくあります。例えば、以下の例では、引数のDisplay
トレイトを用いてプリントを行うため、T
がDisplay
を持っていることを規定しています。つまり、「T
はDisplay
を実装 していなくてはならない 」という意味です。
// Define a function `printer` that takes a generic type `T` which
// must implement trait `Display`.
// `Display`トレイトを実装している`T`を引数として取る
// `printer`という関数を定義。
fn printer<T: Display>(t: T) {
println!("{}", t);
}
境界は、ジェネリクスを全ての型ではなく、一定条件を満たす型に対してのみ適用するためにあります。つまり
訳注:
<T: Display>
は<T>
の部分集合であることを意識すると、「境界」という言葉の意味がしっくり来ると思います。
struct S<T: Display>(T);
// Error! `Vec<T>` does not implement `Display`. This
// specialization will fail.
// エラー! `Vec<T>`は`Display`を実装していないため、この特殊化
// は失敗します。
let s = S(vec![1]);
境界のもう一つの効果は、ジェネリック型のインスタンスが、境界条件となるトレイトのメソッドにアクセスすることができるようになる点です。以下がその例です。
付け加えておくと、where
句を用いて境界を適用することもできます。場合によってはこちらの記法を使用したほうが読みやすくなる場合もあります。
参照
テストケース: 空トレイト
トレイト境界の仕組みから、「トレイトがなにも機能を持っていなくとも境界条件として使用できることには変わりはない」という帰結がもたらされます。Eq
とCopy
はstd
ライブラリにおけるそのような例です。
参照
std::cmp::Eq
, std::marker::Copy
, トレイト
複数のジェネリック境界
+
を用いて1つの型に複数のトレイト境界を設けることができます。複数の引数を受け取るときは、通常時と同様、,
で区切ります。
参照
Where句
トレイト境界は、{
の直前にwhere
句を導入することでも設けることができます。where
はさらに、型パラメータだけでなく任意の型に対してのみ適用できます。
where
句のほうが有効なケースには例えば
- ジェネリック型とジェネリック境界に別々に制限を加えたほうが明瞭になる場合 つまり、
impl <A: TraitB + TraitC, D: TraitE + TraitF> MyTrait<A, D> for YourType {}
// Expressing bounds with a `where` clause
// `where`を用いてジェネリック境界を設ける。
impl <A, D> MyTrait<A, D> for YourType where
A: TraitB + TraitC,
D: TraitE + TraitF {}
where
句の方が通常の構文より表現力が高い場合
があります。
参照
RFC, 構造体, トレイト, エラーハンドリングの日本語による解説記事
ニュータイプイディオム
既に定義済みの型であってもstruct
を使うことであたかも別の型newtype
であるかのように定義することが可能です。なお、それらが正しい型としてプログラムに供給されているか否かは、コンパイル時に保証されます。
例えば、年齢を年単位で確認するold_enough
には「Years」という型の値を 与えなければならない ようにすることが可能です。
最後の print文 のコメントを外して、与えられた型が Years
でなければならないことを確認してください。
newtype
の元に使われている型のデータを取得するには、以下のようにタプルや分配構文を用いることで取得できます。
参照
関連型
関連要素(Associated Items)とは複数の型の要素(items)に関係のある規則の総称です。トレイトの拡張機能であり、トレイトの中で新しい要素を定義することを可能にします。
そのように定義する要素の一つに 関連型 があります。これにより、ジェネリックなコンテナ型に対するトレイトを使用する際に、よりシンプルな書き方ができるようになります。
参照
関連型が必要になる状況
コンテナ型に、その要素に対してジェネリックなトレイトを実装した場合、そのトレイトを使用する者は全てのジェネリック型を明記 しなくてはなりません 。
以下の例ではContains
トレイトはジェネリック型A
とB
の使用を許しています。その後、Container
型に対してContains
を実装していますが、その際後にfn difference()
が使用できるように、A
、B
はそれぞれi32
と明記されています。
Contains
はジェネリックトレイトなので、fn difference()
では 全ての ジェネリック型を宣言しなくてはなりません。実際のところ、A
とB
は 引数 であるC
によって決定されていて欲しいにも関わらず、です。これは次のページで紹介する関連型と呼ばれる機能によって可能です。
参照
関連型
関連型を使用すると、コンテナ型の中の要素をトレイトの中に 出力型 として書くことで、全体の可読性を上げることができます。トレイトを定義する際の構文は以下のようになります。
Contains
トレイトを使用する関数において、A
とB
を明示する必要がなくなっていることに注目しましょう。
// Without using associated types
// 関連型を使用しない場合
fn difference<A, B, C>(container: &C) -> i32 where
C: Contains<A, B> { ... }
// Using associated types
// 使用する場合
fn difference<C: Contains>(container: &C) -> i32 { ... }
前セクションの例を関連型を使用して書きなおしてみましょう。
幽霊型パラメータ
幽霊型(Phantom Type)とは実行時には存在しないけれども、コンパイル時に静的に型チェックされるような型のことです。
構造体などのデータ型は、ジェネリック型パラメータを一つ余分に持ち、それをマーカーとして使ったりコンパイル時の型検査に使ったりすることができます。このマーカーは実際の値を何も持たず、したがって実行時の挙動そのものにはいかなる影響ももたらしません。
以下の例では、そのようなマーカーとして幽霊型(std::marker::PhantomData)を用い、それぞれ異なった型の値を持つタプルを作成します。
参照
導出(Derive
), 構造体, タプル
テストケース: 単位を扱う
共通の単位同士を扱う際のチェックのために、Add
を幽霊型を用いた実装にすると便利な場合があります。その場合Add
トレイトは以下のようになります。
訳注: RHSはRight Hand Side、つまりAdd(
+
)演算時の右辺のことです
// This construction would impose: `Self + RHS = Output`
// where RHS defaults to Self if not specified in the implementation.
// このように定義しておくと、`Self + RHS = Output`であることが保証され、
// かつ、impl時にRHSの型が明示されていない場合、デフォルトでSelfと同じに
// なる。
pub trait Add<RHS = Self> {
type Output;
fn add(self, rhs: RHS) -> Self::Output;
}
// `Output` must be `T<U>` so that `T<U> + T<U> = T<U>`.
// `Output`は`T<U>`でなくてはならないので`T<U> + T<U> = T<U>`となる。
impl<U> Add for T<U> {
type Output = T<U>;
...
}
以下は全体を示した例です。:
参照
借用(&
), トレイトバウンド, 列挙型, impl & self,
演算子のオーバーロード, 参照, トレイト (X for Y
), タプル.
スコーピングの規則
所有権、借用、ライフタイムといったRustに特有の概念において、変数のスコープは重要な役割を果たします。すなわち、スコープの存在によってコンパイラは借用は可能か否か、メモリ上の資源は解放可能か、変数はいつ作成され、いつ破棄されるか。といったことを知るのです。
RAII
Rustの変数は単にデータをスタック上に保持するだけのものではありません。例えばヒープメモリを確保するBox<T>
のように、変数はメモリ上の資源を 保有 する場合もあるのです。RustはRAII(Resource Acquisition Is Initialization)を強制するので、オブジェクトがスコープを抜けると、必ずデストラクタが呼び出されてそのオブジェクトが保持していた資源が解放されます。
この振る舞いは リソースリーク (resource leak
)バグを防ぐのに役立ちます。手動でメモリを解放したり、メモリリークバグにわずらわされたりすることはなくなるのです!簡単な例で説明しましょう。
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
関数内の変数がスコープを抜けるときにカスタムデストラクタが呼び出されるはずです。
参照
所有権とムーブ
変数には自身の保持する資源を開放する責任があるため、 資源は一度に一つの所有者 しか持つことができません。これはまた、資源を2度以上開放することができないということでもあります。ここで、全ての変数が資源を所有するわけではないということに注意しましょう。(e.g. 参照)
変数をアサインする(let x = y
)際や、関数に引数を値渡しする(foo(x)
)際は、資源の 所有権(ownership
) が移動します。Rustっぽく言うと、「 ムーブ 」です。
資源を移動すると、それまでの所有者(訳注: 変数などのこと)を使用することはできなくなります。これによりダングリングポインタの発生を防げます。
ミュータビリティ
データのミュータビリティは所有権を移譲した際に変更できます。
部分的ムーブ
1つの変数の デストラクト の中で、 ムーブ
と 参照
の両方のパターンバインディングを同時に使用することができます。両方を使用すると、変数の一部がムーブされ、他の部分が参照として残るという変数の部分的ムーブが発生した状態になります。変数の部分的ムーブが発生すると親変数はその後使用できませんが、参照されているだけの部分(ムーブされていない部分)は使用することができます。
この例では、age
変数をヒープ上に保持し、部分的ムーブを説明しています。
上記コードでref
を削除すると、person.age
の所有権がage
変数にムーブされるため、エラーになります。
もしもPerson.age
がスタック上に保持されていたら、
age
の定義がperson.age
をムーブすることなくデータをコピーするので、
ref
は必須ではないのですが、実際にはヒープ上に保持されているためref
は必須です。
参照
借用
実際には、データの所有権を完全に受け渡すことなく一時的にアクセスしたいという場合がほとんどです。そのために、Rustでは 借用(borrowing
) という仕組みを用います。値そのもの(T
)を受け渡すのではなく、そのリファレンス(&T
)を渡すのです。
コンパイラは借用チェッカを用いてリファレンスが 常に 有効なオブジェクトへの参照であることを、コンパイル時に保証します。つまり、あるオブジェクトへのリファレンスが存在しているならば、そのオブジェクトを破壊することはできないということです。
ミュータビリティ
ミュータブルなデータは&mut T
でミュータブルに(変更可能な形で)借用することができます。これは ミュータブルな参照 と呼ばれ、読み込み・書き込みの権限を借用者に与えます。対照的に&T
はデータをイミュータブルな参照を介して借用し、借用した側はデータを読み込みはできますが書き込みはできません。
参照
エイリアス
データは一度にいくつでもイミュータブルに借用することができますが、その間オリジナルのデータをミュータブルに借用することはできません。一方でミュータブルな借用は一度に 一つ しか借用することができません。オリジナルのデータをもう一度借用できるのはミュータブルな参照が最後に使われた場所より あとで なければいけません。
refパターン
let
を介してデストラクトやパターンマッチングを行う場合、ref
キーワードを用いて構造体・タプルのフィールドへのリファレンスを取得することができます。以下の例ではこれが有用になる例を幾つかお見せします。
ライフタイム
ライフタイム はコンパイラ(借用チェッカーと呼ばれる場合もあります)が、全ての借用に問題がないことを確認するために使用する仕組みです。正確にいうと、変数のライフタイムは作成時に開始し、破棄された時に終了します。ライフタイムとスコープは同時に語られることが多いですが、同じものではありません。
例として&
を用いて変数を借用する場合をあげましょう。借用のライフタイムは宣言時に決定し、そこから貸し手が破棄されるまで続きます。しかしながら、借用のスコープは参照が使われる際に決定します。
以下の例からこのセクションの残りでは、ライフタイムとスコープの関係、そしてそれらがいかに異なるものであるかを見ていきます。
ここで、一切の名前や型がライフタイムにアサインされていないことに注意しましょう。これにより、ライフタイムの使われ方がこれから見ていくようなやり方に制限されます。
明示的アノテーション
借用チェッカーは参照がどれだけの間有効かを決定するために、明示的なアノテーションを使用します。ライフタイムが省略1されなかった場合、Rustは参照のライフタイムがどのようなものであるか、明示的なアノテーションを必要とします。
foo<'a>
// `foo` has a lifetime parameter `'a`
// `foo`は`'a`というライフタイムパラメータを持ちます。
クロージャと同様、ライフタイムの使用はジェネリクスを必要とします。もう少し詳しく言うと、この書き方は「foo
のライフタイムは'a
のそれを超えることはない。」ということを示しており、型を明示した場合'a
は&'a T
となるということです。
ライフタイムが複数ある場合も、同じような構文になります。
foo<'a, 'b>
// `foo` has lifetime parameters `'a` and `'b`
// `foo`は`'a`と`'b`というライフタイムパラメータを持ちます。
この場合は、foo
のライフタイムは'a
、'b
の いずれよりも 長くなってはなりません。
以下はライフタイムを明示的に書く場合の例です。
省略 はライフタイムが暗黙のうちに(プログラマから見えない形で)アノテートされることを指します。
参照
関数
省略をしない場合、ライフタイムのシグネチャ(e.g. <'a>
)を持つ関数にはいくつかの制限があります。
- 全ての変数においてライフタイムを明示しなくてはならない。
- 返り値となる参照はすべて引数と同じライフタイムか、
static
ライフタイムを持たなくてはならない
加えて、引数のない関数から参照を返す場合、それが結果的に無効なデータへの参照になるならば、禁止されている
参照
メソッド
メソッドのライフタイムは関数に似ている。
参照
構造体
構造体におけるライフタイムも関数のそれと似ている。
参照
トレイト
トレイトのメソッドにおけるライフタイムのアノテーションは、
基本的には関数に似ています。
impl
にもライフタイムのアノテーションがあることに注意してください。
参照
ライフタイム境界
ジェネリック型に境界(bound)を与え、特定のトレイトを実装していることを保証できるのと同様、ライフタイム(それ自身ジェネリック型)にも境界を与えることができます。:
は、ここでは多少異なる意味を持ちますが+
は同じです。以下の構文の意味をチェックしてください。
T: 'a
:T
内の 全ての 参照は'a
よりも長生きでなくてはならないT: Trait + 'a
: 上に加えてT
はTrait
という名のトレイトを実装してなくてはならない。
上記の構文を実際に動く例で見ていきましょう。where
キーワードの後に注目してください。
参照
圧縮
長いライフタイムは、短いものに圧縮(coerce)することで、そのままでは動作しないスコープの中でも使用できるようになります。これは、Rustコンパイラが推論の結果として圧縮する場合と、複数のライフタイムを比較して圧縮する場合があります。
スタティックライフタイム
Rustにはいくつかの予約されたライフタイム名があります。その1つがstatic
で、2つの状況で使用することがあります。
2つの状況におけるstatic
は微妙に異なる意味を持っており、Rustを学ぶときの混乱の元になっています。
いくつかの例とともにそれぞれの使い方を見てみましょう。
参照のライフタイム
参照のライフタイムが'static
であることは、参照が指し示す値がプログラムの実行中に渡って生き続けることを示します。
また、より短いライフタイムに圧縮することも可能です。
'static
ライフタイムを持つ変数を作るには下記の2つ方法があります。
どちらの場合も、値は読み取り専用のメモリ領域に格納されます。
static
宣言とともに定数を作成する。- 文字列リテラルで
&'static str
型を持つ変数を作成する。
では、それぞれの方法の例を見ていきましょう。
トレイト境界
トレイト境界としての'static
は型が非静的な参照を含まないことを意味します。
言い換えると、レシーバはその型をいくらでも長く保持することができ、意図的にドロップするまでは決して無効になることはないということです。
次のポイントを押さえておきましょう。所有権のある値が'static
ライフタイム境界をパスするとしても、その値への参照が'static
ライフタイム境界をパスするとは限りません。
コンパイラのメッセージはこのようになります、
error[E0597]: `i` does not live long enough
エラー[E0597]: `i`は十分なライフタイムを持っていません
--> src/lib.rs:15:15
|
15 | print_it(&i);
| ---------^^--
| | |
| | borrowed value does not live long enough
| | 借用した値のライフタイムが不足
| argument requires that `i` is borrowed for `'static`
| 引数は`i`が`'static`として借用されることを要求する
16 | }
| - `i` dropped here while still borrowed
| `i`は借用されたままここでドロップされる
参照
省略
ライフタイムのパターンのうちのいくつかは、他と比べてあまりにも一般的に使用されるため、タイプ量を減らし可読性を上げるために省くことができます。これは省略として知られており、それらのパターンが一般的であるというだけの理由で存在しています。
以下のコードでは省略の例を幾つかお見せします。より完全な説明を見たい場合は、「プログラミング言語Rust」のライフタイムの省略の項を見てください。
参照
トレイト
トレイト(trait
)とは任意の型となりうるSelf
に対して定義されたメソッドの集合のことです。同じトレイト内で宣言されたメソッド同士はお互いにアクセスすることができます。
トレイトはあらゆるデータ型に実装することができます。以下の例ではまずAnimal
というメソッドの集合を定義し、その後Animal
トレイトをSheep
というデータ型に対して実装します。これによりAnimal
のメソッドをSheep
が使用することが可能になります。
導出(Derive)
コンパイラには、#[derive]
アトリビュートを用いることで型に対して特定のトレイトの標準的な実装を提供する機能があります。より複雑なことを行わせたい場合には、同名のトレイトを手動で実装することも可能です。
以下はderive可能なトレイトの一覧です。
- 型の比較に関連するトレイト:
Eq
,PartialEq
,Ord
,PartialOrd
Clone
, これはコピーによって&T
からT
を作成するトレイトCopy
, to give a type 'copy semantics' instead of 'move semantics'.Hash
, これは&T
からハッシュ値を計算するためのトレイトDefault
, これは空っぽのインスタンスを作成するためのトレイトDebug
, これは{:?}
フォーマッタを利用して値をフォーマットするためのトレイト
参照
dyn
を利用してトレイトを返す
Rustのコンパイラはあらゆる関数のリターン型に必要なスペースを知っておく必要があります。
つまり、すべての関数は具体的な型を返す必要があるのです。
他の言語と違って、Animal
のようなトレイトがある場合に、Animal
を返す関数を書くことはできません。
なぜなら、そのトレイトの異なる実装はそれぞれ別の量のメモリを必要とするからです。
しかし、簡単な回避策があります。
直接トレイトオブジェクトを返す代わりに、Animal
を 含む Box
を返すのです。
Box
はヒープ中のメモリへの単なる参照です。
参照のサイズは静的に知ることができ、コンパイラは参照がヒープに割り当てられたAnimal
を指していると保証できるので、私たちは関数からトレイトを返すことができます。
ヒープにメモリを割り当てる際、Rustは可能な限り明示的であろうとします。
なので、もしあなたの関数がヒープ上のトレイトへのポインタを返す場合、例えばBox<dyn Animal>
のように、リターン型にdyn
キーワードをつける必要があります。
演算子のオーバーロード
Rustでは、多くの演算子はトレイトによってオーバーロードすることができます。つまり、一部の演算子は引数となる値の型に応じて異なる役割を果たすことができるということです。これが可能なのは、演算子が実際にはメソッド呼び出しの糖衣構文にすぎないからです。例えばa + b
における+
演算子はadd
メソッドを(a.add(b)
の形で)呼び出します。このadd
メソッドはAdd
トレイトの一部です。それ故、+
はAdd
トレイトを実装している全ての型に対して有効なのです。
Add
などの、演算子をオーバーロードするトレイトの一覧はcore::ops
にあります。
See Also
メモリ解放
Drop
トレイトにはメソッドが一つだけしかありません。drop
です。これは、オブジェクトがスコープから抜けた時に自動で呼ばれます。Drop
トレイトの主な使用目的は、インスタンスが所有する資源を開放することです。
Drop
トレイトを実装している型の例としてはBox
、Vec
、String
、File
、Process
等があげられます。Drop
トレイトは任意の型に対して手動で実装することができます。
以下の例ではdrop
メソッドにコンソールへの出力を追加することで、drop
が呼ばれたタイミングが分かるようにしています。
イテレータ
Iterator
トレイトは、例えば配列のような、要素の集合に対してイテレータを実装するためのトレイトです。
このトレイトはnext
の要素に相当するものを決定するためのメソッドのみを要求します。このメソッドはimpl
ブロック内で手動で実装するか、あるいは(配列やrangeのように)自動で定義されます。
サッとイテレータを使いたい時は、for
文で集合からイテレータを作成することが良くあります。これは.into_iter()
メソッドを呼び出しています。
impl Trait
impl Trait
は2つの利用方法があります:
- 引数の型
- リターン型
引数の型
あなたの関数がジェネリックなトレイトを使用していて、特定の型を意識していない場合、impl Trait
を引数の型として利用して、関数宣言をシンプルにできます。
例えば、次のコードを考えてみましょう:
parse_csv_document
はジェネリックなので、BufReadを実装する任意の型を取ることができます。
例えば、BufReader<File>
や[u8]
です。
R
がどんな型かは重要ではなく、src
の型宣言に使われているだけなので、この関数は以下のように書くこともできます:
impl Trait
を引数の型として利用するということは、どのような形式の関数であるか明示できないので、注意してください。
例えば、parse_csv_document::<std::io::Empty>(std::io::empty())
は2番目の例では動作しません。
リターン型
あなたの関数がMyTrait
を実装する型を返す場合、
リターン型を-> impl MyTrait
のように書けます。
これで型シグネチャをとてもシンプルにできます。
より重要なことに、Rustの型には書き表せないものがあるのです。
例えば、あらゆるクロージャは独自の無名な具象型を持ちます。
impl Trait
構文がない時は、クロージャを返すにはヒープ上に置かねばなりませんでした。
しかし今では次のようにすべて静的に行えます。
impl Trait
を使って、map
やfilter
クロージャを使うイテレータを返すこともできます。
おかげでmap
やfilter
を簡単に使えます。
クロージャ型は名前を持たないので、あなたの関数がクロージャを持つイテレータを返す場合、
明示的なリターン型を書くことはできません。
しかしimpl Trait
を使うことで簡単にできます:
クローン
メモリ上の資源を扱う際、変数束縛や関数呼び出しを介して移動させるのがデフォルトの挙動です。しかしながら、場合によっては資源のコピーを作るのが適切なこともあります。
Clone
トレイトはまさにこのためにあります。普通はClone
トレイトで定義されている.clone()
を用います。
スーパートレイト
Rustには"継承"はありませんが、あるトレイトを別のトレイトの上位集合として定義できます。 例えば:
参照
The Rust Programming Language chapter on supertraits
Disambiguating overlapping traits
A type can implement many different traits. What if two traits both require
the same name? For example, many traits might have a method named get()
.
They might even have different return types!
A type can implement many different traits. What if two traits both require the same name? 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.
参照
The Rust Programming Language chapter on Fully Qualified syntax
macro_rules!
Rustはメタプログラミングを可能にする、パワフルなマクロシステムを備えています。これまで見てきたように、マクロは!
で終わることを除けば関数のように見えます。関数と違うのは関数呼び出し(function call
)を生成する代わりに、ソースコード中に展開され、周囲のプログラムとともにコンパイルされる点です。
しかし、Cやその他の言語のマクロが文字列のプリプロセッシングをするのと異なり、Rustのマクロは抽象構文木へと展開されるので、予期せぬprecendece(演算子の優先順位)のバグに出くわすことがありません。
マクロを作成するにはmacro_rules!
というマクロを使用します。
ではどうしてマクロは便利なのでしょうか?
- 同じことを繰り返し書いてはいけない (Don't repeat yourself) から。 複数の場所で、別の型だけれど似たような機能が必要な時がよくあります。 しばしば、マクロはコードを繰り返し書くのを避ける有用な手段なのです(あとで詳述)。
- ドメイン特化言語であるから。マクロを使うと、特定の目的のための特定の構文を定義することができます(あとで詳述)。
- 可変個引数によるインターフェース。
取る引数の数が可変であるようなインターフェースを定義したくなることもあるでしょう。
例えば、
println!
は、フォーマット文字列に依存した任意の数の引数を取ることができます(あとで詳述)!
構文
以下のサブセクションでは、Rustにおいてマクロを定義する方法を示します。 3つの基本的な考え方があります:
識別子
macroの引数は$
が頭につきます。型は 識別子 (designator
)でアノテーションされます。
使用できる識別子には以下のようなものがあります。
block
expr
式に使用ident
関数、変数の名前に使用item
literal
はリテラル定数(訳注:文字だけではない。Literal expressionsを参照)に使用pat
(パターン)path
stmt
(宣言)tt
(トークンツリー)ty
(型)vis
(可視性修飾子)(訳注:pub (crate)
とか)
完全なリストを見るには、Rustリファレンスを読んでください。
オーバーロード
マクロは異なる引数の組み合わせを取るようにオーバーロードすることができるため、macro_rules!
はマッチと似たような使い方をすることができます。
繰り返し
マクロは引数のリストの中で+
を使うことができ、そうすることによって、引数が少なくとも1回以上繰り返されるということを示すことができます。同様に*
の場合は、0以上を示します。
以下の例では、マッチ対象を $(...),+
で囲むことにより、カンマで区切られた1つ以上の式とマッチします。最後のセミコロンは必須ではないことに注目しましょう。
DRY (Don't Repeat Yourself)
マクロは関数やテストなどにおいて、共通の部分を抽出することでDRYなコードを書くのに役立ちます。ここではVec<T>
に+=
、*=
、-=
を実装、テストするにあたって、マクロがどのように役立つかを見ていきます。
$ 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を定義したいとしましょう。 式を与えると、出力がコンソールに書き出されるようにしたいです。
出力はこうなります:
1 + 2 = 3
(1 + 2) * (3 / 4) = 0
これはとても単純な例ですが、lazy_static
やclap
のように、もっと複雑なインターフェースも開発されています。
また、マクロの中に2組の括弧があることにも注目してください。
外側のは、()
や[]
に加え、macro_rules!
の構文の一部です。
可変個引数によるインターフェース
可変個引数のインターフェースとは、任意の数の引数を取るものです。
例えば、println!
は、フォーマット文字列の定義に従い、任意の数の引数を取ることができます。
前のセクションのcalculate!
マクロを、可変個引数に拡張することができます:
出力:
1 + 2 = 3
3 + 4 = 7
(2 * 3) + 1 = 7
エラーハンドリング
エラーハンドリングとは失敗の起きる可能性を扱うプロセスのことです。例えば、ファイルを読み込むのに失敗した際、その 誤った インプットを使い続けるのは明らかに問題です。そのようなエラーを通知して明示的に扱うことで、残りのプログラムに問題が波及することを防ぐことができるようになります。
Rustには、これからこの章で見ていく通り、エラーを処理するための様々な方法が存在します。それらは全て僅かに異なり、ユースケースも異なります。経験則として:
明示的なpanic
はテストや復旧不可能なエラーに対して効果的です。プロトタイプにも便利で、例えば未実装の関数を扱う時などに有効ですが、このような場合にはより叙述的なunimplemented
の方が良いでしょう。テストにおいてはpanic
は明示的にテストを失敗させるための良い手法になるでしょう。
Option
型は値があるとは限らない場合や、値が無いことがエラーの条件とならない場合に有効です。例えば親ディレクトリ(/
やC:
はそれを持ちません)などです。Option
を扱う際は、unwrap
がプロトタイプや値が確実に存在することが約束されるケースに使えます。しかし、expect
の方が何かが上手くいかなかった際にエラーメッセージを指定することができるため、より便利でしょう。
何かが上手くいかない可能性があったり、呼び出し元が問題を処理しなければならない時は、Result
を使いましょう。unwrap
やexpect
を実行することもできます(テストや短期的なプロトタイプ以外では使わないでください)。
より詳細なエラーハンドリングに関する議論については、オフィシャルブックの該当の章を参考にしてください。
訳注: こちらのQiitaの日本語記事も参考になります。「RustでOption値やResult値を上手に扱う」
panic
panic
は、最もシンプルなエラーハンドリングの仕組みです。エラーメッセージの出力、スタックの巻き戻し、そして多くの場合プログラムの終了を実行します。
例として、エラー条件に対して明示的にpanic
を呼び出してみましょう。
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.
Here is another example focusing on rewriting drink()
and explicitly use the unwind
keyword.
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
できるように扱っていきます。
?
によるOption
のアンパック
Option
をアンパックするにはmatch
文を使うこともできますが、?
を使う方が簡単になることが多いでしょう。Option
のx
があるとすると、x?
を評価した値は、x
がSome
の場合はx
に格納された値となり、そうでなければ実行中の関数を終了させ、None
を返します。
多くの?
を共に使うことで、リーダブルなコードを書くことができます。
Combinators: map
match
はOption
を扱うのに適したメソッドです。しかし、大量にこれを使用しているとじきに億劫になってくるでしょう。引数の値が有効である(訳注: この場合はNone
でない)必要がある関数を扱う際には特にそうです。
そうした場合には、コンビネータを使うと、処理の流れをモジュール化されたやり方で管理できます。
Some -> Some
あるいはNone -> None
の単純な操作を適用する必要がある場合には、Option
はmap()
というビルトインのメソッドを提供していますので、これを使用しましょう。
map()
のフレキシビリティは、複数のmap()
をチェインしなければならない場合にさらに際立ちます。
以下の例では、process()
が直前の関数全てを用いた場合と同じ機能を、よりコンパクトに果たしているのがわかります。
参照
closures, Option
, Option::map()
Combinators: 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()
には不適切な型です。
参照
closures, Option
, Option::and_then()
, and 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.
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:
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:
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:
See also:
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()
した場合の成功例と失敗例を見てみましょう。
失敗例では、parse()
がエラーを返すためunwrap()
がパニックします。そして、panic
はプログラムを終了させて不快なエラーメッセージを出力します。
エラーメッセージを改善するために、リターン型に対してもっと明確になるべきで、またエラーを明示的に処理することを考えるべきです。
main
内で使うResult
Result
型は、明示的な指定によりmain
関数のリターン型にもなります。一般に、main
関数は以下のような形になるでしょう。
fn main() {
println!("Hello World!");
}
一方main
でResult
をリターン型とすることも可能です。エラーがmain
関数内で発生した時、エラーコードを返し、エラーに関するデバッグ表記を(Debug
トレイトを使って)出力します。以下の例ではそのようなシナリオを示し、この先の節でカバーする内容に触れていきます。
Result
のmap
前の例で見たmultiply
でのパニックは、コードを強固にするためには書きません。一般に、呼び出した側がエラーをどのように対処するべきかを自由に決められるように、エラーを呼び出した場所に返すのが好ましいです。
まずは、どのようなエラー型を扱っているのかを知る必要があります。Err
型を定めるために、i32
に対しFromStr
トレイトを使って実装されたparse()
を見てみましょう。結果、Err
型はParseIntError
というものであることが分かります。
以下の例では、単純なmatch
文が全体として扱いづらいコードにしています。
幸運にも、Option
のmap
、and_then
、その他多くのコンビネータもResult
のために実装されています。Result
に全てのリストが記載されています。
Result
に対するエイリアス
特定のResult
型を何度も使いたくなるのはどんな時でしょう?Rustはエイリアスの作成をサポートしていたことを思い出してください。便利なことに、特定のResult
型に対しても定義することができます。
モジュールレベルでは、エイリアスの作成は非常に役に立ちます。特定のモジュールで見られるエラーは同じErr
型を持つため、単一のエイリアスで簡潔にResults
に関わる全てを定義できます。std
ライブラリが提供するもの(io::Result
)もあるほど有益なのです!
簡単な例で構文を見てみましょう。
参照
早期リターン
前の例では、コンビネータの活用によりエラーを明示的に処理しました。場合分けに対する別の対処法として、match
文と早期リターンを組み合わせて使うこともできます。
つまり、エラーが発生した時点で関数の実行を止め、エラーを返してしまうという単純な方法が使えるということです。この方法の方がより読みやすく書きやすい場合があります。早期リターンを使って実装された、前の例の新たなバージョンを考えてみましょう。
ここまでで、コンビネータと早期リターンによる明示的なエラーハンドリングについて学びました。しかし、パニックは一般に避けたいですが、全てのエラーを明示的に処理するのも厄介でしょう。
次の節では、panic
を発生させずにunwrap
する必要があるケースのための?
について紹介していきます。
?
の導入
時にはpanic
の可能性を無視して、unwrap
のシンプルさを活用したいこともあるでしょう。今までのunwrap
は、値を取り出すためだけであろうとも、ネストを深く書くことを要求しました。そして、これがまさに?
の目的です。
Err
を見つけるにあたり、2つのとるべき行動があります。
- 可能な限り避けたいと決めた
panic!
Err
は処理できないことを意味するためreturn
?
はほぼ1まさしく、Err
に対してpanic
するよりreturn
するという点でunwrap
と同等です。コンビネータを使った以前の例をどれだけ簡潔に書けるか見てみましょう。
try!
マクロ
?
ができる前、同様の動作をtry!
マクロによって行うことができました。現在は?
オペレータが推奨されていますが、古いコードではtry!
に出会うこともあります。try!
を使って前の例と同じmultiply
関数を実装すると、以下のようになるでしょう。
詳細はre-enter ?を参照。
複数のエラー型
Result
が他のResult
と連携したり、Option
が他のOption
と連携するなど、今までの例はとても便利な物でした。
時にはOption
がResult
と連携したり、Result<T, Error1>
がResult<T, Error2>
と連携する必要もあるでしょう。そのような場面では、異なるエラー型を構成しやすく、かつ連携しやすく管理したいです。
以下のコードはunwrap
の2つのインスタンスが異なるエラー型を生成します。Vec::first
はOption
を返し、一方でparse::<i32>
はResult<i32, ParseIntError>
を返しています。
この先の節では、これらの問題を処理する方法について見ていきます。
Option
からResult
を取り出す
混在するエラー型に対する最も基本的な対処法は、単にお互いを埋め込んでしまうことです。
中には、Option
の中身がNone
の場合はそのまま処理を進め、エラーの検出に限り実行を止めたいという場合もあるでしょう(?
を使った時のように)。いくつかのコンビネータによって簡単にResult
とOption
をスワップすることができます。
エラー型を定義する
異なるエラー型をマスクし単一のエラー型として扱えるようにすると、コードがシンプルになる場合があります。ここでは自前のエラー型でそれを示してみます。
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())
- 良い例:
- 他のエラーと問題なく連携できる
エラーをBox
する
元のエラーを維持しながらシンプルなコードを書くには、Box
してしまうと良いでしょう。欠点として、元のエラー型はランタイムまで判明せず、静的に決定されないことが挙げられます。
標準ライブラリはBox
に、From
を介してあらゆるError
トレイトを実装した型からBox<Error>
トレイトオブジェクトへの変換を実装させることで、エラーをboxしやすくしてくれます。
参照
Dynamic dispatch and Error
trait
?
の他の活用法
以前の例では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
は消えてなくなります。
これでかなり綺麗になりました。元のpanic
と比べ、リターン型がResult
であることを除けば、unwrap
の呼び出しを?
で置き換えたものに非常に似ています。結果、そのResult
は上のレベルで分解されなければなりません。
参照
From::from
and ?
エラーをラップする
Boxする方法の代替として、エラーを自前のエラー型としてラップする方法もあります。
これはエラーの処理のボイラープレートを増やしてしまい、全てのアプリケーションで必要になる訳では無いでしょう。これらのボイラープレートの処理を代わりにやってくれるようなライブラリもあります。
参照
From::from
and Enums
Result
をイテレートする
Iter::map
オペレーションは失敗することもあります。例えば、
ここでは、この対処法についてみてみましょう。
filter_map()
を使って失敗した要素のみを無視する
filter_map
は関数を呼び出し、結果がNone
になるものだけ取り除きます。
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.
collect()
で処理全体を失敗させる
Result
は、それらのベクトル(Vec<Result<T, E>>
)からベクトルのそれ(Result<Vec<T>, E>
)へと変換できるようにするため、FromIterator
を実装します。Result::Err
が見つかり次第、イテレーションは終了します。
同じテクニックは、Option
を用いて行うこともできます。
partition()
を使って全ての正常な値と失敗をまとめる
結果を見てみると、まだ全てResult
にラップされていることに気づくでしょう。もう少しのボイラープレートが必要です。
標準ライブラリの型
std
ライブラリは、基本データ型を劇的に拡張するカスタム型を数多く提供します。例えば以下です。
- 拡張可能な文字列である
String
。例えば:"hello world"
- オプション型:
Option<i32>
- エラーハンドリング用の
Result<i32, i32>
- ヒープ上資源のポインタ
Box<i32>
参照
Box, スタックとヒープ
Rustにおいて、すべての値はデフォルトでスタックに割り当てられます。Box<T>
を作成することで、値を ボックス化 、すなわちヒープ上に割り当てることができます。ボックスとは正確にはヒープ上におかれたT
の値へのスマートポインタです。ボックスがスコープを抜けると、デストラクタが呼ばれて内包するオブジェクトが破棄され、ヒープメモリが解放されます。
ボックス化された値は*
オペレータを用いてデリファレンスすることができます。これにより一段と直接的な操作が可能になります。
ベクタ型
「ベクタ」はサイズを変更可能な配列です。スライスと同様、そのサイズはコンパイル時には不定ですが、いつでも要素を追加したり削除したりすることができます。ベクタは3つの要素で、その特徴が完全に決まります。
- データへのポインタ
- 長さ
- 容量 ... あらかじめメモリ上にベクタのために確保された領域
ベクタはその容量を超えない限りにおいて長くしていくことができます。超えた場合には、より大きな容量を持つように割り当てなおされます。
Vec
型のメソッドの一覧はstd::vecモジュールを見てください。
文字列
Rustには文字列を扱う型が2つあります。String
と&str
です。
String
は有効なUTF-8の配列であることを保証されたバイトのベクタ(Vec<u8>
)として保持されます。ヒープ上に保持され、伸長可能で、末端にnull文字を含みません。
&str
は有効なUTF-8の配列のスライス(&[u8]
)で、いつでもString
に変換することができます。&[T]
がいつでもVec<T>
に変換できるのと同様です。
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: "\""
, '\''
.
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.
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!
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
をラップするタプルです。
Result
これまでの例で、失敗する可能性のある関数の返り値として、列挙型Option
が使用でき、失敗時の返り値にはNone
を用いることを見てきました。しかし、時には なぜ そのオペレーションが失敗したのかを明示することが重要な場合があります。そのためにはResult
列挙型を使用します。
列挙型Result<T, E>
は2つの値をとりえます。
Ok(value)
... これはオペレーションが成功したことを意味し、返り値value
をラップします。(value
は型T
を持ちます。)Err(why)
... これはオペレーションの失敗を意味します。why
をラップしており、ここには失敗した理由が(必ずではありませんが)書かれています。(why
の型はE
です。)
?
マッチを利用して結果をチェインするのは中々面倒です。
幸いなことに、?
マクロを使用すればイケてるコードに戻すことができます。
?
はResult
を返す式の末尾で使います。
Err(err)
の分岐がreturn Err(From::from(err))
という早期リターンに展開され、
Ok(ok)
の分岐がok
の式に展開されるようなマッチ式と等価です。
公式ドキュメントをチェックすることをオススメします。
Result
型を扱う関数やResult
型のメソッドが多く挙げられています。
panic!
panic!
マクロはパニックを生成し、スタックの巻き戻しを開始するために使用することができます。巻き戻しの間、ランタイムは、(訳注: panicを起こした)スレッドが 所有権を持つ 全ての資源のデストラクタを呼び出し、メモリ上から解放します。
今回はシングルスレッドのプログラムを実行しているので、panic!
はプログラムにパニックメッセージを表示させ、exitします。
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()
を用います。後者が推奨されています。
ハッシングやハッシュマップ(ハッシュテーブルと呼ばれることもあります)の仕組みについて、より詳しく知りたい場合はWikipediaのハッシュテーブルのページを見てください。
key型の変種
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
で扱う際の例として、とてもシンプルなユーザーログインシステムを作成してみましょう。
ハッシュ集合
値がなく、キーだけのHashMap
を想像してみてください。これはハッシュ集合(HashSet
)と呼ばれるものです。(HashSet<T>
は、実際にはHashMap<T, ()>
のラッパーです。)
「何の意味があるの?フツーにキーをVec
に入れればいいじゃん」そう思いましたね?
それは、HashSet
独自の機能として、要素に重複がないということが保証されるためです。これは全ての集合(set
)型がもつ機能です。HashSet
はその実装の1つであり、他にはBTreeSet
等があります。
HashSet
に、すでに存在する値を加えようとすると、(すなわち、加えようとしている値のハッシュ値と、要素中のいずれかの値のハッシュ値が等しい場合、)新しい値によって古い値が上書きされます。
これは、同じ値を2つ以上欲しくない場合や、すでにある値を持っているか知りたい場合にとても有効です。
しかし、集合型の機能はそれだけではありません。
集合型には4つの主要なメソッドがあり、(すべてイテレータを返します。)
union
: 2つの集合型のどちらか一方にある値を全て取得
difference
: 1つ目の集合にあり、かつ2つ目には存在しない値を全て取得。
intersection
: 両方の集合にある値のみを取得。
symmetric_difference
: どちらか一方の集合には存在するが、両方には ない 値を取得
以下の例でこれらをすべて見ていきましょう。
例は公式ドキュメントから持ってきています。
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.
参照
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.
When shared ownership between threads is needed, Arc
(Atomic 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.
標準ライブラリのその他
他にも、様々な型がstdライブラリの中で提供されています。例えば以下の機能を果たすための物があります。
- スレッド
- チャネル
- ファイルI/O
これらにより基本データ型の提供する機能よりも遥かに豊かなことが実現できます。
参照
スレッド
Rustはspawn
関数を用いてOSのネイティブスレッドを開始することができます。この関数の引数はmoveクロージャ(訳注: 参照ではなく値を取るクロージャ。 詳しくはクロージャを返す関数を参照)です。
これらのスレッドのスケジューリングはOSによって行われる。
Testcase: map-reduce
Rust makes it very easy to parallelise 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 synchronisation is needed,
there are synchronisation
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.)
we're move
-ing the data segments into the thread, Rust will also ensure the data is kept alive
until the threads exit, so no dangling pointers occur.
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.
参照
- Threads
- vectors and iterators
- closures, move semantics and
move
closures - destructuring assignments
- turbofish notation to help type inference
- unwrap vs. expect
- enumerate
チャネル
Rustは、スレッド間のコミュニケーションのために、非同期のチャネル(channels
)を提供しています。チャネル2つのエンドポイント、すなわち送信者(Sender
)と受信者(Receiver
)を介して、情報の一方向への流れを作り出すことを可能にしています。
Path
構造体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
によって無条件でできます。
他のPath
メソッド(posix::Path
とwindows::Path
)をチェックするのを忘れずに!それとMetadata
構造体も見ておくことをオススメします。
参照
ファイル I/O
File
構造体は開かれたファイルを表し(実際にはファイルディスクリプタのラッパーです)、読み込み・書き込み権限のどちらか一方、あるいは両方を提供します。
これはI/Oに関するオペレーションの失敗をより明瞭にします。このおかげでプログラマは直面した失敗を全て見ることができ、より生産的な方法でそれらを扱うことが可能になります。
open
open
関数を用いることで読み込み専用モードでファイルを開くことが可能です。
File
はファイルディスクリプタという資源を保持しており、drop
時にはファイルを閉じるところまで面倒を見てくれます。
以下が成功時に期待されるアウトプットです。
$ 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::error::Error;
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();
// Open a file in write-only mode, returns `io::Result<File>`
// ファイルを書き込み専用モードで開く。返り値は`io::Result<File>`
let mut file = match File::create(&path) {
Err(why) => panic!("couldn't create {}: {}", display, why),
Ok(file) => file,
};
// Write the `LOREM_IPSUM` string to `file`, returns `io::Result<()>`
// `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),
}
}
以下は成功時に期待されるアウトプットです。
$ mkdir out
$ rustc create.rs && ./create
successfully wrote to lorem_ipsum.txt
$ cat lorem_ipsum.txt
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
前項の例と同じように、様々な失敗パターンをためしてみることをオススメします。
OpenOptions
構造体を利用して、ファイルの開き方を設定できます。
read_lines
単純なやり方
テキストファイルの行を読み込むのを、初心者が初めて実装した場合、 以下のようになるでしょう。
lines()
メソッドはファイルの各行のイテレータを返すので、
インラインでマップを実行し結果を収集することもできます。
そうすると、より簡潔で読みやすい表現となります。
上の例では、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() {
// File hosts.txt must exist in the current path
// hosts.txtファイルは現在のパスに存在しなければならない。
if let Ok(lines) = read_lines("./hosts.txt") {
// Consumes the iterator, returns an (Optional) String
// イテレータを消費し、Option型のStringを返す。
for line in lines {
if let Ok(ip) = line {
println!("{}", ip);
}
}
}
}
// The output is wrapped in a Result to allow matching on errors
// Returns an Iterator to the Reader of the lines of the file.
// 出力は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
構造体はプロセスの作成を行います。
(余裕があれば、上の例でrustc
に不正なフラグを渡し、どうなるか見てみましょう)
パイプ
std::Child
構造体は実行中の子プロセスを表します。stdin
、stdout
、stderr
を介して表面化のプロセスとのやり取りを仲介します。
use std::error::Error;
use std::io::prelude::*;
use std::process::{Command, Stdio};
static PANGRAM: &'static str =
"the quick brown fox jumped over the lazy dog\n";
fn main() {
// Spawn the `wc` command
// `wc`コマンドを起動する。
let process = match Command::new("wc")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn() {
Err(why) => panic!("couldn't spawn wc: {}", why),
Ok(process) => process,
};
// Write a string to the `stdin` of `wc`.
// `wc`の`stdin`に文字列を書き込む。
//
// `stdin` has type `Option<ChildStdin>`, but since we know this instance
// must have one, we can directly `unwrap` it.
// `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"),
}
// Because `stdin` does not live after the above calls, it is `drop`ed,
// and the pipe is closed.
//
// This is very important, otherwise `wc` wouldn't start processing the
// input we just sent.
// `stdin`は上のプロセスコールのあとには有効でないので、`drop`され、
// パイプはcloseされる。
// (これは非常に重要です。というのもcloseしないと`wc`は
// 送った値の処理を開始しないからです。)
// The `stdout` field also has type `Option<ChildStdout>` so must be unwrapped.
// `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),
}
}
dropの延期
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` keeps running for 5 seconds until the `sleep 5` command finishes
# `wait`は`sleep 5`コマンドが終了するまで5秒間実行され続ける。
reached end of main
ファイルシステムとのやり取り
std::fs
モジュールはファイルシステムとやり取りするための関数をいくつか持っています。
use std::fs;
use std::fs::{File, OpenOptions};
use std::io;
use std::io::prelude::*;
use std::os::unix;
use std::path::Path;
// A simple implementation of `% cat 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),
}
}
// A simple implementation of `% echo s > path`
// `% echo s > path`の簡単な実装
fn echo(s: &str, path: &Path) -> io::Result<()> {
let mut f = File::create(path)?;
f.write_all(s.as_bytes())
}
// A simple implementation of `% touch path` (ignores existing files)
// `% 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`");
// Create a directory, returns `io::Result<()>`
// ディレクトリを作成する。返り値は`io::Result<()>`
match fs::create_dir("a") {
Err(why) => println!("! {:?}", why.kind()),
Ok(_) => {},
}
println!("`echo hello > a/b.txt`");
// The previous match can be simplified using the `unwrap_or_else` method
// 上のmatchは`unwrap_or_else`をメソッドを用いて簡略化できる。
echo("hello", &Path::new("a/b.txt")).unwrap_or_else(|why| {
println!("! {:?}", why.kind());
});
println!("`mkdir -p a/c/d`");
// Recursively create a directory, returns `io::Result<()>`
// 再帰的にディレクトリを作成する。返り値は`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`");
// Create a symbolic link, returns `io::Result<()>`
// シンボリックリンクを作成、返り値は`io::Result<()>`
if cfg!(target_family = "unix") {
unix::fs::symlink("../b.txt", "a/c/b.txt").unwrap_or_else(|why| {
println!("! {:?}", why.kind());
});
}
println!("`cat a/c/b.txt`");
match cat(&Path::new("a/c/b.txt")) {
Err(why) => println!("! {:?}", why.kind()),
Ok(s) => println!("> {}", s),
}
println!("`ls a`");
// Read the contents of a directory, returns `io::Result<Vec<Path>>`
// ディレクトリの内容を読み込む。返り値は`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`");
// Remove a file, returns `io::Result<()>`
// ファイルを削除。返り値は`io::Result<()>`
fs::remove_file("a/c/e.txt").unwrap_or_else(|why| {
println!("! {:?}", why.kind());
});
println!("`rmdir a/c/d`");
// Remove an empty directory, returns `io::Result<()>`
// 空のディレクトリを削除。返り値は`io::Result<()>`
fs::remove_dir("a/c/d").unwrap_or_else(|why| {
println!("! {:?}", why.kind());
});
}
以下が成功時に期待されるアウトプットです。
$ rustc fs.rs && ./fs
`mkdir a`
`echo hello > a/b.txt`
`mkdir -p a/c/d`
`touch a/c/e.txt`
`ln -s ../b.txt a/c/b.txt`
`cat a/c/b.txt`
> hello
`ls a`
> "a/b.txt"
> "a/c"
`rm a/c/e.txt`
`rmdir a/c/d`
最終的なa
ディレクトリの状態は以下です。
$ tree a
a
|-- b.txt
`-- c
`-- b.txt -> ../b.txt
1 directory, 2 files
別のやり方でcat
関数を定義するには、?
記法を使います:
fn cat(path: &Path) -> io::Result<String> {
let mut f = File::open(path)?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
参照
引数処理
Standard Library
コマンドライン引数はstd::env::args
を介して取得できます。これはそれぞれの引数を文字列としてyieldするイテレータを返します。
$ ./args 1 2 3
My path is ./args.
I got 3 arguments: ["1", "2", "3"].
Crates
Alternatively, there are numerous crates that can provide extra functionality
when creating command-line applications. The Rust Cookbook exhibits best
practices on how to use one of the more popular command line argument crates,
clap
.
引数のパース
matchを用いて簡単な引数をパースできます。
$ ./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;
// this extern block links to the libm library
// このexternブロックはlibmライブラリをリンクする。
#[link(name = "m")]
extern {
// this is a foreign function
// that computes the square root of a single precision complex number
// 他言語の関数宣言。
// この関数は単精度浮動小数の複素数型の平方根を計算するためのもの
fn csqrtf(z: Complex) -> Complex;
fn ccosf(z: Complex) -> Complex;
}
// Since calling foreign functions is considered unsafe,
// it's common to write safe wrappers around them.
// 型安全にするためのラッパ
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));
}
// Minimal implementation of single precision complex numbers
// 単精度浮動小数の複素数型の最小限の実装
#[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 ガイドライン のドキュメンテーション
ユニットテスト
テストは、テスト以外のコードが想定通りに動いているかを確かめる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
}
// This is a really bad adding function, its purpose is to fail in this
// example.
// 誤った加算をする関数がテストに通らないことを示す。
#[allow(dead_code)]
fn bad_add(a: i32, b: i32) -> i32 {
a - b
}
#[cfg(test)]
mod tests {
// Note this useful idiom: importing names from outer (for mod tests) scope.
// 外部のスコープから(mod testsに)名前をインポートする便利なイディオム。
use super::*;
#[test]
fn test_add() {
assert_eq!(add(1, 2), 3);
}
#[test]
fn test_bad_add() {
// This assert would fire and test will fail.
// Please note, that private functions can be tested too!
// このアサーションはパニックして、テストは失敗する。
// プライベートな関数もテストすることができる。
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<()>
を返し、内部で?
を使えるようになりました!これにより、ユニットテストをさらに簡潔に記述できます。
詳細はエディションガイドを参照してください。
パニックをテストする
ある条件下でパニックすべき関数をテストするには、#[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
で、除外したテストのみを実行できます。
$ 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は正確性を重視しているので、コードブロックもコンパイルされ、テストとして使われます。
/// First line is a short summary describing function.
/// 最初の行には関数の機能の短い要約を書きます。
///
/// The next lines present detailed documentation. Code blocks start with
/// triple backquotes and have implicit `fn main()` inside
/// and `extern crate <cratename>`. Assume we're testing `doccomments` crate:
/// 以降で詳細なドキュメンテーションを記述します。コードブロックは三重のバッククォートで始まり、
/// 暗黙的に`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
}
/// Usually doc comments may include sections "Examples", "Panics" and "Failures".
/// 一般的に、ドキュメンテーションコメントは「実行例」「パニック」「失敗」という章から成る。
///
/// The next function divides two numbers.
/// 次の関数は除算を実行する。
///
/// # Examples
/// # 実行例
///
/// ```
/// let result = doccomments::div(10, 2);
/// assert_eq!(result, 5);
/// ```
///
/// # Panics
/// # パニック
///
/// The function panics if the second argument is zero.
/// 第2引数がゼロであればパニックする。
///
/// ```rust,should_panic
/// // panics on division by zero
/// // ゼロで除算するとパニックする
/// doccomments::div(10, 0);
/// ```
pub fn div(a: i32, b: i32) -> i32 {
if b == 0 {
panic!("Divide-by-zero error");
}
a / b
}
ドキュメンテーションコメント中のコードブロックは、cargo test
コマンドで自動的にテストされます。
$ cargo test
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Doc-tests doccomments
running 3 tests
test src/lib.rs - add (line 7) ... ok
test src/lib.rs - div (line 21) ... ok
test src/lib.rs - div (line 31) ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
ドキュメンテーションテストの目的
ドキュメンテーションテストの主な目的は、実行例を示すことであり、これは最も大切なガイドラインの一つにもなっています。これにより、ドキュメントの例を実際に動くコードとして使うことができます。しかしながら、main
が()
を返すために、?
を使うとコンパイルに失敗してしまいます。ドキュメンテーションでコードブロックの一部を隠す機能で、この問題に対処できます。つまり、fn try_main() -> Result<(), ErrorType>
を定義しておきながらそれを隠し、暗黙のmain
の内部でunwrap
するのです。複雑なので、例を見てみましょう。
/// Using hidden `try_main` in doc tests.
/// ドキュメンテーションテストで、`try_main`を隠して使う。
///
/// ```
/// # // hidden lines start with `#` symbol, but they're still compilable!
/// # // 行頭に `#` を置くと行が隠されるが、コンパイルには成功する。
/// # fn try_main() -> Result<(), String> { // line that wraps the body shown in doc
/// # // ドキュメントの本体を囲う行
/// let res = doccomments::try_div(10, 2)?;
/// # Ok(()) // returning from try_main
/// # // try_mainから値を返す
/// # }
/// # fn main() { // starting main that'll unwrap()
/// # // unwrap()を実行するmain
/// # try_main().unwrap(); // calling try_main and unwrapping
/// # // so that test will panic in case of error
/// # // 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
:
// Define this in a crate called `adder`.
// Assume that crate is called adder, will have to extern it in integration test.
// `adder`という名前のクレートの内部で、次の関数を定義する。
// インテグレーションテストでadderクレートをexternで宣言する。
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
テストを含むファイルtests/integration_test.rs
:
// extern crate we're testing, same as any other code would do.
// 他の外部のコードと同様に、テスト対象のクレートをexternで宣言する。
extern crate adder;
#[test]
fn test_add() {
assert_eq!(adder::add(3, 2), 5);
}
cargo test
コマンドでテストを実行します。
$ cargo test
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Running target/debug/deps/integration_test-bcd60824f5fbfe19
running 1 test
test test_add ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Doc-tests adder
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
tests
ディレクトリにあるRustのソースファイルは別のクレートとしてコンパイルされます。インテグレーションテストの間でコードを共有するには、パブリックな関数をモジュールに入れて、それぞれのテストでインポートして利用する方法があります。
ファイルtests/common.rs
:
pub fn setup() {
// some setup code, like creating required files/directories, starting
// servers, etc.
// 必要なファイル・ディレクトリの作成やサーバの起動といった準備を行うコードを記述する。
}
テストを含むファイルtests/integration_test.rs
:
// extern crate we're testing, same as any other code will do.
// 他の外部のコードと同様に、テスト対象のクレートをexternで宣言する。
extern crate adder;
// importing common module.
// 共通のモジュールをインポートする。
mod common;
#[test]
fn test_add() {
// using common code.
// 共通のコードを利用する。
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
:
# standard crate data is left out
# 本節の内容に関係のない行は省略しています。
[dev-dependencies]
pretty_assertions = "1"
ファイルsrc/lib.rs
:
// externing crate for test-only use
// テストにのみ使うクレートをexternで宣言する
#[cfg(test)]
#[macro_use]
extern crate pretty_assertions;
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq; // crate for test-only use. Cannot be used in non-test code.
// テストのためのクレートであり、テスト以外のコードには使えない。
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
}
参照
依存関係の記述については、Cargoのドキュメントを参照してください。
安全でない操作
この章の内容を見る前に、公式ドキュメントから引用した以下の文章をお読みください。
コードベース中の、アンセーフな操作をするコードの量は、可能な限り小さく無くてはならない。
この戒めを頭に叩き込んだ上で、さあはじめましょう! Rustにおいて、アンセーフなブロックはコンパイラのチェックをスルーするために使われます。具体的には以下の4つの主要なユースケースがあります。
- 生ポインタのデリファレンス
- 安全でない関数やメソッドの呼び出し(FFI経由の関数の呼び出しを含む (詳細は 本書のFFIに関する説明 を参照ください))
- 静的なミュータブル変数へのアクセスや変更
- 安全でないトレイトの実装
生ポインタ
生ポインタ*
と参照&T
はよく似た機能を持ちますが、後者は必ず有効なデータを指していることが借用チェッカーによって保証されているので、常に安全です。生ポインタのデリファレンスはアンセーフなブロックでしか実行できません。
安全でない関数呼び出し
関数は unsafe
として宣言できます。これはコンパイラの代わりにプログラマの責任で正しさを保証することを意味します。
例として std::slice::from_raw_parts
があります。この関数は最初の要素へのポインタと長さを指定してスライスを作成します。
slice::from_raw_parts
は、以下の仮定に基づいて処理します。
- 渡されたポインタが有効なメモリ位置を指していること
- そのメモリに格納された値が正しい型であること
この仮定を満たさない場合、プログラムの動作は不定となり、何が起こるかわかりません。
インラインアセンブリ
Rustはasm!
マクロによってインラインアセンブリをサポートしています。
コンパイラが生成するアセンブリに、手書きのアセンブリを埋め込むことができます。
一般的には必要ありませんが、要求されるパフォーマンスやタイミングを達成するために必要な場合があります。
カーネルコードのような、低レベルなハードウェアの基本要素にアクセスする場合にも、この機能が必要でしょう。
注意: 以下の例はx86/x86-64アセンブリで書かれていますが、他のアーキテクチャもサポートされています。
インラインアセンブリは現在以下のアーキテクチャでサポートされています。
- x86とx86-64
- ARM
- AArch64
- RISC-V
基本的な使い方
最も単純な例から始めましょう。
これは、コンパイラが生成したアセンブリに、NOP (no operation) 命令を挿入します。
すべてのasm!
呼び出しは、unsafe
ブロックの中になければいけません。
インラインアセンブリは任意の命令を挿入でき、不変条件を壊してしまうからです。
挿入される命令は、文字列リテラルとしてasm!
マクロの第一引数に列挙されます。
入力と出力
何もしない命令を挿入しても面白くありません。 実際にデータを操作してみましょう。
これはu64
型の変数x
に5
の値を書き込んでいます。
命令を指定するために利用している文字列リテラルが、実はテンプレート文字列になっています。
これはRustのフォーマット文字列と同じルールに従います。
ですが、テンプレートに挿入される引数は、みなさんがよく知っているものとは少し違っています。
まず、変数がインラインアセンブリの入力なのか出力なのかを指定する必要があります。
上記の例では出力となっています。
out
と書くことで出力であると宣言しています。
また、アセンブリが変数をどの種類のレジスタに格納するかについても指定する必要があります。
上の例では、reg
を指定して任意の汎用レジスタに格納しています。
コンパイラはテンプレートに挿入する適切なレジスタを選び、インラインアセンブリの実行終了後、そのレジスタから変数を読みこみます。
入力を利用する別の例を見てみましょう。
この例では、変数i
の入力に5
を加え、その結果を変数o
に書き込んでいます。
このアセンブリ特有のやり方として、はじめにi
の値を出力にコピーし、それから5
を加えています。
この例はいくつかのことを示します。
まず、asm!
では複数のテンプレート文字列を引数として利用できます。
それぞれの文字列は、改行を挟んで結合されたのと同じように、独立したアセンブリコードとして扱われます。
このおかげで、アセンブリコードを容易にフォーマットできます。
つぎに、入力はout
ではなくin
と書くことで宣言されています。
そして、他のフォーマット文字列と同じように引数を番号や名前で指定できます。 インラインアセンブリのテンプレートでは、引数が2回以上利用されることが多いため、これは特に便利です。 より複雑なインラインアセンブリを書く場合、この機能を使うのが推奨されます。 可読性が向上し、引数の順序を変えることなく命令を並べ替えることができるからです。
上記の例をさらに改善して、mov
命令をやめることもできます。
inout
で入力でもあり出力でもある引数を指定しています。
こうすることで、入力と出力を個別に指定する場合と違って、入出力が同じレジスタに割り当てられることが保証されます。
inout
のオペランドとして、入力と出力それぞれに異なる変数を指定することも可能です。
遅延出力オペランド
Rustコンパイラはオペランドの割り当てに保守的です。
out
はいつでも書き込めるので、他の引数とは場所を共有できません。
しかし、最適なパフォーマンスを保証するためには、できるだけ少ないレジスタを使うことが重要です。
そうすることで、インラインアセンブリブロックの前後でレジスタを保存したり再読み込みしたりする必要がありません。
これを達成するために、Rustはlateout
指定子を提供します。
全ての入力が消費された後でのみ書き込まれる出力に利用できます。
この指定子にはinlateout
という変化形もあります。
以下は、release
モードやその他の最適化された場合に、inlateout
を利用 できない 例です。
上記はDebug
モードなど最適化されていない場合にはうまく動作します。
しかし、release
モードなど最適化されたパフォーマンスが必要な場合、動作しない可能性があります。
というのも、最適化されている場合、コンパイラはb
とc
が同じ値だと知っているので、
b
とc
の入力に同じレジスタを割り当てる場合があります。
しかし、a
についてはinlateout
ではなくinout
を使っているので、独立したレジスタを割り当てる必要があります。
もしinlateout
が使われていたら、a
とc
に同じレジスタが割り当てられたかもしれません。
そうすると、最初の命令によってc
の値が上書きされ、アセンブリコードが間違った結果を引き起こします。
しかし、次の例では、全ての入力レジスタが読み込まれた後でのみ出力が変更されるので、inlateout
を利用できます。
このアセンブリコードは、a
とb
が同じレジスタに割り当てられても、正しく動作します。
明示的なレジスタオペランド
いくつかの命令では、オペランドが特定のレジスタにある必要があります。
したがって、Rustのインラインアセンブリでは、より具体的な制約指定子を提供しています。
reg
は一般的にどのアーキテクチャでも利用可能ですが、明示的レジスタはアーキテクチャに強く依存しています。
たとえば、x86の汎用レジスタであるeax
、ebx
、ecx
、edx
、ebp
、esi
、edi
などは、その名前で指定できます。
この例では、out
命令を呼び出して、cmd
変数の中身を0x64
ポートに出力しています。
out
命令はeax
とそのサブレジスタのみをオペランドとして受け取るため、
eax
の制約指定子を使わなければなりません。
注意: 他のオペランドタイプと異なり、明示的なレジスタオペランドはテンプレート文字列中で利用できません。
{}
を使えないので、レジスタの名前を直接書く必要があります。 また、オペランドのリストの中で他のオペランドタイプの一番最後に置かれなくてはなりません。
x86のmul
命令を使った次の例を考えてみましょう。
mul
命令を使って2つの64ビットの入力を128ビットの結果に出力しています。
唯一の明示的なオペランドはレジスタで、変数a
から入力します。
2つ目のオペランドは暗黙的であり、rax
レジスタである必要があります。変数b
からrax
レジスタに入力します。
計算結果の下位64ビットはrax
レジスタに保存され、そこから変数lo
に出力されます。
上位64ビットはrdx
レジスタに保存され、そこから変数hi
に出力されます。
クロバーレジスタ
多くの場合、インラインアセンブリは出力として必要のない状態を変更することがあります。 これは普通、アセンブリでスクラッチレジスタを利用する必要があったり、 私たちがこれ以上必要としていない状態を命令が変更したりするためです。 この状態を一般的に"クロバー"(訳注:上書き)と呼びます。 私たちはコンパイラにこのことを伝える必要があります。 なぜならコンパイラは、インラインアセンブリブロックの前後で、 この状態を保存して復元しなくてはならない可能性があるからです。
上の例では、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
がかわりに利用されるでしょう。
アセンブリコード内部で利用するスクラッチレジスタを獲得するために、 汎用レジスタクラスとともに使用することもできます。
シンボル・オペランドとABIクロバー
デフォルトでは、asm!
は、出力として指定されていないレジスタはアセンブリコードによって中身が維持される、と考えます。
asm!
に渡されるclobber_abi
引数は、与えられた呼び出し規約のABIに従って、
必要なクロバーオペランドを自動的に挿入するようコンパイラに伝えます。
そのABIで完全に保存されていないレジスタは、クロバーとして扱われます。
複数の clobber_abi
引数を指定すると、指定されたすべてのABIのクロバーが挿入されます。
レジスタテンプレート修飾子
テンプレート文字列に挿入されるレジスタの名前のフォーマット方法について、細かい制御が必要な場合があります。 アーキテクチャのアセンブリ言語が、同じレジスタに別名を持っている場合です。 典型的な例としては、レジスタの部分集合に対する"ビュー"があります(例:64ビットレジスタの下位32ビット)。
デフォルトでは、コンパイラは常に完全なレジスタサイズの名前を選択します(例:x86-64ではrax
、x86ではeax
、など)。
この挙動は、フォーマット文字列と同じように、テンプレート文字列のオペランドに修飾子を利用することで上書きできます。
この例では、reg_abcd
レジスタクラスを用いて、レジスタアロケータを4つのレガシーなx86レジスタ (ax
, bx
, cx
, dx
) に制限しています。このうち最初の2バイトは独立して指定できます。
レジスタアロケータがx
をax
レジスタに割り当てることにしたと仮定しましょう。
h
修飾子はそのレジスタの上位バイトのレジスタ名を出力し、l
修飾子は下位バイトのレジスタ名を出力します。
したがって、このアセンブリコードはmov ah, al
に展開され、値の下位バイトを上位バイトにコピーします。
より小さなデータ型(例:u16
)をオペランドに利用し、テンプレート修飾子を使い忘れた場合、
コンパイラは警告を出力し、正しい修飾子を提案してくれます。
メモリアドレスオペランド
アセンブリ命令はオペランドがメモリアドレスやメモリロケーション経由で渡される必要なこともあります。
そのときは手動で、ターゲットのアーキテクチャによって指定されたメモリアドレスのシンタックスを利用しなくてはなりません。
例えば、Intelのアセンブリシンタックスを使うx86/x86_64の場合、入出力を[]
で囲んで、メモリオペランドであることを示さなくてはなりません。
ラベル
名前つきラベルの再利用は、ローカルかそうでないかに関わらず、アセンブラやリンカのエラーを引き起こしたり、変な挙動の原因となります。 名前つきラベルの再利用は以下のようなケースがあります。
- 明示的再利用: 同じラベルを1つの
asm!
ブロック中で、または複数のブロック中で2回以上利用する場合です。 - インライン化による暗黙の再利用: コンパイラは
asm!
ブロックの複数のコピーをインスタンス化する場合があります。例えば、asm!
ブロックを含む関数が複数箇所でインライン化される場合です。 - LTO(訳注: Link Time Optimizationの略)による暗黙の再利用: LTOは 他のクレート のコードを同じコード生成単位に配置するため、同じ名前のラベルを持ち込む場合があります。
そのため、インラインアセンブリコードの中では、GNUアセンブラの 数値型 ローカルラベルのみ使用してください。 アセンブリコード内でシンボルを定義すると、シンボル定義の重複により、アセンブラやリンカのエラーが発生する可能性があります。
さらに、x86でデフォルトのIntel構文を使用する場合、LLVMのバグによって、
0
、11
、101010
といった0
と1
だけで構成されたラベルは、
バイナリ値として解釈されてしまうため、使用してはいけません。
options(att_syntax)
を使うと曖昧さを避けられますが、asm!
ブロック 全体 の構文に影響します。
(options
については、後述のオプションを参照してください。)
このコードは、{0}
のレジスタの値を10から3にデクリメントし、2を加え、a
にその値を保存します。
この例は、以下のことを示しています。
- まず、ラベルとして同じ数字を複数回、同じインラインブロックで利用できます。
- つぎに、数字のラベルが参照として(例えば、命令のオペランドに)利用された場合、"b"("後方")や"f"("前方")の接尾辞が数字のラベルに追加されなくてはなりません。そうすることで、この数字の指定された方向の最も近いラベルを参照できます。
オプション
デフォルトでは、インラインアセンブリブロックは、カスタム呼び出し規約をもつ外部のFFI関数呼び出しと同じように扱われます: メモリを読み込んだり書き込んだり、観測可能な副作用を持っていたりするかもしれません。 しかし、多くの場合、アセンブリコードが実際に何をするかという情報を多く与えて、より最適化できる方が望ましいでしょう。
先ほどのadd
命令の例を見てみましょう。
オプションはasm!
マクロの最後の任意引数として渡されます。
ここでは3つのオプションを利用しました:
pure
は、アセンブリコードが観測可能な副作用を持っておらず、出力は入力のみに依存することを意味します。これにより、コンパイラオプティマイザはインラインアセンブリの呼び出し回数を減らしたり、インラインアセンブリを完全に削除したりできます。nomem
は、アセンブリコードがメモリの読み書きをしないことを意味します。デフォルトでは、インラインアセンブリはアクセス可能なメモリアドレス(例えばオペランドとして渡されたポインタや、グローバルなど)の読み書きを行うとコンパイラは仮定しています。nostack
は、アセンブリコードがスタックにデータをプッシュしないことを意味します。これにより、コンパイラはx86-64のスタックレッドゾーンなどの最適化を利用し、スタックポインタの調整を避けることができます。
これにより、コンパイラは、出力が全く必要とされていない純粋なasm!
ブロックを削除するなどして、asm!
を使ったコードをより最適化できます。
利用可能なオプションとその効果の一覧はリファレンスを参照してください。
互換性
Rust言語は急速に進化しており、可能な限り前方互換性を確保する努力にも関わらず、特定の互換性の問題が生じることがあります。
生識別子
Rustは多くのプログラミング言語と同様に、「キーワード」の概念を持っています。 これらの識別子は言語にとって何かしらの意味を持ちますので、変数名や関数名、その他の場所で使用することはできません。 生識別子は、通常は許されない状況でキーワードを使用することを可能にします。 これは、新しいキーワードを導入したRustと、古いエディションのRustを使用したライブラリが同じ名前の変数や関数を持つ場合に特に便利です。
例えば、2015年エディションのRustでコンパイルされたクレートfoo
がtry
という名前の関数をエクスポートしているとします。
このキーワードは2018年エディションで新機能として予約されていますので、生識別子がなければ、この関数を名付ける方法がありません。
extern crate foo;
fn main() {
foo::try();
}
このエラーが出ます:
error: expected identifier, found keyword `try`
--> src/main.rs:4:4
|
4 | foo::try();
| ^^^ expected identifier, found keyword
これは生識別子を使って書くことができます:
extern crate foo;
fn main() {
foo::r#try();
}
周辺情報
この章では、プログラミングそれ自体に関係はないけれども、色々と人々の役に立つ機能やインフラについて説明していきます。例えば:
- ドキュメンテーション: Rust付属コマンド
rustdoc
を用いて、ライブラリのドキュメントを生成します。 - プレイグラウンド: あなたのドキュメンテーションにRust Playgroundを組み込めます。
ドキュメンテーション
Use cargo doc
to build documentation in target/doc
.
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.
Doc comments
ドキュメンテーションコメントとはrustdoc
を使用した際にドキュメントにコンパイルされるコメントのことです。///
によって普通のコメントと区別され、ここではMarkdownを使用することができます。ドキュメンテーションコメントは大規模なプロジェクトの際に非常に有用です。
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
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:
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
では、コード例を実行・編集可能にできます。
これにより、読者はあなたのコード例を実行するだけでなく、変更することもできます。
editable
という単語をカンマで区切って、あなたのコードブロックに追加するのがキーです。
```rust,editable
//...ここにあなたのコードを書きます
```
加えて、mdbook
がビルドやテストを実行するときに、あなたのコードをスキップさせたい場合は、ignore
を追加できます。
```rust,editable,ignore
//...ここにあなたのコードを書きます
```
ドキュメントと組み合わせる
Rustの公式ドキュメントには、「実行(Run)」ボタンがある場合があります。
クリックすると、新しいタブでRust Playgroundが開き、コード例が表示されます。
この機能は、#[doc]アトリビュートのhtml_playground_url
の値で有効化できます。