Rust by Example 日本語版

Rust は安全性、速度、並列性にフォーカスした現代的なシステムプログラミング 用のプログラミング言語です。 ガベージコレクション無しでメモリ安全であることが、これを可能にしています。

Rust by Example(RBE)はRustの実行可能なサンプルスクリプト集で、ここではRustの様々な コンセプトと標準ライブラリを紹介していきます。 この例をより活用するためにはRustをローカルにインストールし、公式ドキュメントをチェックすることをおすすめします。 興味がある方はこのサイト自体のソースのチェックもどうぞ。

訳注: 日本語版のソースコードはこちらにあります。

それでははじめましょう!

  • Hello World - お決まりのHello Worldプログラムから始めましょう。
  • 基本データ型 - 符号付き整数や符号無し整数、その他の基本データ型について学びましょう。
  • 変数の束縛 - ミュータブルな束縛、スコープ、シャドーイングについて。
  • - 型を変更したり定義したりすることを学びましょう。
  • 関数 - メソッド、クロージャ、高階関数について。
  • モジュール - プログラムをモジュールを使って整理しましょう。
  • クレート - クレートは、Rustにおいてコンパイルされる単位です。ライブラリの作り方について学びます。
  • Cargo - Rustの公式パッケージマネージャの基本的な機能を学びます。
  • アトリビュート - アトリビュートは、モジュールやクレート、要素に適用されるメタデータです。
  • ジェネリクス - 様々な型の引数を取れる関数やデータ型を書く方法を学びましょう。
  • スコープの規則 - スコープは所有権、借用、ライフタイムにおいて重要な役割を果たします。
  • トレイト - トレイトとは、未知の型Selfに対して定義された一連のメソッドです。
  • テスト - Rustにおけるテストのすべて。
  • 周辺情報 - ドキュメント、ベンチマークの方法。

Hello World

ここでは伝統的な"Hello World!"プログラムのソースを紹介します。 

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

println!は文字列をコンソールにプリントするための マクロ です。

バイナリファイルはrustcと呼ばれるRustのコンパイラを用いて生成することができます。

$ rustc hello.rs

するとhelloという名前の実行可能なバイナリファイルができます。

$ ./hello Hello World!

演習

上に書いている'Run'をクリックしてアウトプットを見てみましょう。 次に、println!マクロをもう一行追加してアウトプットがどうなるか見てみましょう。

Hello World! I'm a Rustacean!

コメント

あらゆるプログラムにはコメントが必要です。Rustには何種類かのコメントがあります

  • 通常のコメント これはコンパイラによって完全に無視されます。
    • // 行末までコメントアウト
    • /* ブロックによって囲まれた部分をコメントアウト */
  • ドキュメンテーションコメント ライブラリのドキュメンテーションとしてhtmlにパースされます。
    • /// このコメントの下の内容に関するドキュメントとなります
    • //! このコメントを含むソースのドキュメントになります
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

ライブラリドキュメンテーション

フォーマットしてプリント

プリント関係の機能はstd::fmtで定義される幾つかのマクロによって扱われます。このマクロには以下が含まれます。

  • format!: フォーマットされたテキストを文字列(String)型に書き込みます。
  • print!: format! と同様ですが、コンソール (io::stdout) にそのテキストを出力します。
  • println!: print!: と同じですが改行が付け加えられます。
  • eprint!: format! と同様ですが、標準エラー出力 (io::stderr) にそのテキストを出力します。
  • eprintln!: eprint!と同じですが改行が付け加えられます。

すべて同じやり方でテキストをパースし、正しくフォーマットできるかコンパイル時にチェックします。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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の場合はやはり手動で実装しなくてはなりません。

#![allow(unused)] fn main() { // This structure cannot be printed either with `fmt::Display` or // with `fmt::Debug`. // この構造体は`fmt::Display`、`fmt::Debug`のいずれによっても // プリントすることができません。 struct UnPrintable(i32); // The `derive` attribute automatically creates the implementation // required to make this `struct` printable with `fmt::Debug`. // `derive`アトリビュートは、 // この構造体を`fmt::Debug`でプリントするための実装を自動で提供します。 #[derive(Debug)] struct DebugPrintable(i32); }

stdライブラリの型の場合は、自動的に{:?}によりプリント可能になっています。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

fmt::Debugは確実にプリント可能にしてくれるのですが、一方である種の美しさを犠牲にしています。 Rustは{:#?}による「見栄えの良いプリント」も提供します。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

手動でfmt::Displayを実装することでプリント結果を思い通りにできます。

参照

アトリビュート, derive, std::fmt, 構造体

ディスプレイ

fmt::Debugはコンパクトでクリーンであるようには見えませんね。大抵の場合は、アウトプットの見た目をカスタマイズしたほうが好ましいでしょう。これは{}を使用するfmt::Displayを手動で実装することで可能です。

#![allow(unused)] fn main() { // Import (via `use`) the `fmt` module to make it available. // (`use`を使用し、)`fmt`モジュールをインポートします。 use std::fmt; // Define a structure for which `fmt::Display` will be implemented. This is // a tuple struct named `Structure` that contains an `i32`. // `fmt::Display`を実装するための構造体を定義します。 // これは`Structure`という名前に紐付けられた、`i32`を含むタプルです。 struct Structure(i32); // To use the `{}` marker, the trait `fmt::Display` must be implemented // manually for the type. // `{}` というマーカーを使用するためには、 // この型専用の`fmt::Display`というトレイトが実装されていなくてはなりません。 impl fmt::Display for Structure { // This trait requires `fmt` with this exact signature. // このトレイトは`fmt`が想定通りのシグネチャであることを要求します。 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // Write strictly the first element into the supplied output // stream: `f`. Returns `fmt::Result` which indicates whether the // operation succeeded or failed. Note that `write!` uses syntax which // is very similar to `println!`. // 厳密に最初の要素を、与えられた出力ストリーム `f` に書き込みます。 // `fmt::Result`を返します。これはオペレーションが成功したか否か // を表します。 // `write!`は`println!`に非常によく似た文法を使用していることに注目。 write!(f, "{}", self.0) } } }

fmt::Displayfmt::Debugより綺麗かもしれませんが、stdライブラリの場合は問題が生じます。曖昧な(ambiguous)タイプはどのように表示すれば良いでしょう? 例えば、stdライブラリがあらゆるVec<T>に対して単一のスタイルを提供していた場合、どのようなスタイルに整形すればよいでしょう?以下の2つのどちらかを選ぶべきでしょうか?

  • Vec<path>: /:/etc:/home/username:/bin:で分割)
  • Vec<number>: 1,2,3,で分割)

答えはNOです。あらゆる型に対して理想的なスタイルなどというものはありませんし、stdライブラリによってそれが提供されているわけでもありません。fmt::DisplayVec<T>のようなジェネリックなコンテナ用に定義されているわけではありませんので、このような場合はfmt::Debugを使用するべきです。

ジェネリック でない コンテナ型の場合は、このような問題は生じませんので問題なくfmt::Displayを実装することができます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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はより簡単に実装できます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

演習

上記のプログラムを変更して、ベクタの各要素のインデックスも表示するようにしてみましょう。変更後の出力は次のようになります。

[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)という単一の変数がXo指定なし 、という様々な 引数タイプ (argument type)に応じてフォーマットされています。

フォーマットの機能はそれぞれの引数タイプごとに個別のトレイトを用いて実装されています。 最も一般的なトレイトはDisplayで、これは引数タイプが未指定(たとえば{})の時に呼び出されます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

フォーマット用トレイトの全リスト、及び引数の型はこちらから、引数の型についてはstd::fmtのドキュメンテーションから参照できます。

演習

上にあるソースコード中のColorという構造体のためのfmt::Displayトレイトの実装を追加しましょう。アウトプットは以下のように表示されるはずです。

RGB (128, 255, 90) 0x80FF5A RGB (0, 3, 254) 0x0003FE RGB (0, 0, 0) 0x000000

詰まったら以下の2つがヒントになります。

参照

std::fmt

基本データ型

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は文脈から型を推定することもできます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

std ライブラリ, mut, inference, shadowing

リテラルとオペレータ

整数1、浮動小数点1.2、文字(char)'a'、文字列"abc"、ブーリアンtrue、ユニット()は、リテラルを使用することで明示することが可能です。

また整数型の場合、リテラルの代わりにプレフィックスに0x0o0bを指定することでそれぞれ16進数、8進数、2進数を使うことができます。

可読性のため、_(アンダースコア)を数値リテラルの間に挿入することができます。例えば1_0001000と、0.000_0010.000001とそれぞれ同一です。

また、Rustは1e67.6e-4などの科学的なE表記をサポートしています。 関連型はf64です。

コンパイラに、どのリテラルを使用するのかを教えてあげなくてはなりません。現在の仕様では、リテラルが32ビット符号無し整数であることを伝える場合、u32サフィックスを、符号付き32ビット整数であればi32を使用します。

Rustで使用可能な演算子と、その実行順序は、Cなどの言語のものとほぼ同じです。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

タプル

タプルは異なる型の値の集合です。括弧()を用いて生成します。タプル自体がそのメンバに対する型シグネチャを保持していますので、明示すると(T1, T2, ...)のようになります。タプルは大きさに制限がありませんので、関数が複数の値を返したい時に使われます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

演習

  1. 復習 : 上にあるMatrixという構造体に、fmt::Displayトレイトを追加しましょう。デバッグフォーマット{:?}ではなくディスプレイフォーマット{}でプリントすることができるようになるはずです。

    ( 1.1 1.2 ) ( 2.1 2.2 )

必要に応じてprint displayのページに戻る必要があるかもしれません。

  1. 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]という型シグネチャを持ちます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

カスタム型

Rustでのカスタムデータ型の作成は主に以下の2つのキーワードを介して行われます。

  • struct: 構造体を定義する
  • enum: 列挙型を定義する

const、あるいはstaticというキーワードによって定数を定義することもできます。

構造体

structというキーワードを用いて作成できる構造体には3種類あります。

  • タプル。(すなわちタプルに名前が付いたようなもの)
  • クラシックなC言語スタイルの構造体。
  • ユニット。これはフィールドを持たず、ジェネリック型を扱う際に有効です。
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

演習

  1. Rectangle の面積を計算する rect_area 関数を追加してください。ネストしたデストラクトを使ってみましょう。
  2. Pointf32 を引数とした時に Rectangle を返す square 関数を追加してください。 Rectangle の左下の点が Point になり、f32Rectangle の幅と高さになります。

See also

アトリビュート(attributes), デストラクト

列挙型

列挙型(enum)はいくつかの異なる要素型の中から1つを選ぶような場合に使用します。構造体(struct)の定義を満たすものならば何でもenum の要素型として使用できます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

型エイリアス

型エイリアスを用いると、列挙型の要素型を別名で参照できます。これは列挙型の名前があまりに長かったり、あまりに一般的だったりで改名したい場合に役立ちます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

このやり方がもっともよく見られるのは、implブロックでSelfという別名を使用する場合です。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

列挙型や型エイリアスについて詳しく学びたい人は、この機能が安定してRustに取り込まれた後にstabilization reportを読んでください。

参照

マッチ(match), 関数(fn), 文字列(String), "Type alias enum variants" RFC

use

useを使用すれば変数のスコープを絶対名で指定する必要がなくなる。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

マッチ(match), use

C言語ライクな列挙型

列挙型はC言語の列挙型のような使い方をする事もできます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

キャスト

テストケース: 連結リスト

enumを使用が適切なパターンのひとつに、連結リスト(linked-list)を作成する場合があります。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

ボックス(Box), メソッド

定数

Rustには2種類の定数があり、いずれもグローバルスコープを含む任意のスコープで宣言することができます。また、いずれも型を明示しなくてはなりません。

  • const: 不変の値(通常はこちらを使用する)
  • static: スタティックなライフタイムを持つミュータブル(mut)な値 The static lifetime is inferred and does not have to be specified. Accessing or modifying a mutable static variable is unsafe.
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

const 及び static の RFC, 'static ライフタイム

変数束縛

Rustは静的(static)な型付けゆえに型安全です。変数束縛は宣言時に型を指定できます。とはいえたいていの場合は、コンパイラは変数の型をコンテキストから推測することができますので、型指定の負担を大幅に軽減できます。

値(リテラルなど)はletを用いて変数に束縛することができます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

ミュータビリティ

変数はデフォルトでイミュータブル(変更不可能)ですがmut構文を使用することでミュータブルになります。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

コンパイラはミュータビリティに関するエラーの詳細を出してくれます。

スコープとシャドーイング

変数はスコープを持つため、 ブロック の中に閉じ込められています。ブロックとは{}で囲まれた領域のことです。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

同様に、変数のシャドーイングも可能です。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

宣言

変数の宣言だけを行っておき、初期化(定義)をのちに行うことも可能です。 しかし、最後まで初期化されない変数が生じる可能性があるため、ふつうは同時に行われます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

未初期化の変数があると予期せぬ動作をする場合があるため、コンパイラは変数を初期化してから使用するよう強制します。

値の凍結

データを同じ名前のイミュータブルな変数に束縛しなおすと、データは凍結されます。凍結したデータは、イミュータブルな束縛がスコープ外になるまで変更できません。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Rustには、プリミティブ型やユーザ定義型を定義したり変換したりする様々な方法があります。 この章は以下の内容を扱います:

型キャスト

Rustはプリミティブ型における強制的な型変換を暗黙に行うことはありません。しかし明示的に行うこと(casting)は可能です。その場合asキーワードを使用します。

整数型から整数型へ型変換する場合、C言語で可能なケースの場合はC言語と同じです。 C言語で未定義の場合の挙動も、Rustでは完全に定義されています。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

リテラル

数値型リテラルはサフィックスにより型を指定することが可能です。例えば、42というリテラルに対してi32型を指定するには42i32とします。

サフィックスを指定しない数値型リテラルの場合、その型はどのように使用されるかに依存して決められます。デフォルトでは整数型の場合i32が、浮動小数点型はf64を使用します。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

上のコードには現時点では解説していない考えがいくつか使用されています。気になる方のために簡単に説明をしておきましょう。

  • std::mem::size_of_valは関数ですが、 絶対パス(full path) で呼び出されています。ソースコードは論理的に区切られた モジュール と呼ばれるものにわけられることができます。今回の場合はsize_of_val関数はmemモジュール内で定義されており、memモジュールはstd クレート 内で定義されています。より詳しくはクレート(crates)を参照してください。

型推論

Rustの型推論エンジンはなかなか賢くできています。初期化の際に評価値の型をチェックするだけでなく、その後にどのような使われ方をしているかを見て推論します。以下がその例です。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

このように、変数の型アノテーションは必要ありません。これでコンパイラもプログラマもハッピーですね!

エイリアス

type文を使用することで既存の型に新しい名前(alias)を付けることができます。その場合、名前はUpperCamelCaseでなくてはなりません。さもなくばコンパイラがエラーを出します。唯一の例外はusizef32のようなプリミティブ型です。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

このようにエイリアスを付ける一番の理由はボイラープレートを減らすことです。例えばio::Result<T>型はResult<T, io::Error>の別名です。

参照

アトリビュート

型変換

プリミティブ型同士はキャストを用いて変換できます。

Rustはカスタム型(例えばstructenum)間の変換をトレイトを用いて行います。ジェネリックな型変換にはFromおよびIntoトレイトを使用します。しかし、よくあるケースにおいて、特にStringとの相互の型変換では、特殊なトレイトが使用されます。

FromおよびInto

FromトレイトとIntoトレイトは本質的に結びついており、そのことが実際に実装に反映されています。もし型Aから型Bへの変換ができるのであれば、型Bから型Aへの変換もできると思うのが自然です。

From

Fromトレイトは、ある型に対し、別の型からその型を作る方法を定義できるようにするものです。そのため、複数の型の間で型変換を行うための非常にシンプルな仕組みを提供しています。標準ライブラリでは、基本データ型やよく使われる型に対して、このトレイトが多数実装されています。

例えば、strからStringへの型変換は簡単です。

#![allow(unused)] fn main() { let my_str = "hello"; let my_string = String::from(my_str); }

自作の型に対しても、型変換を定義すれば同じように行えます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Into

Intoトレイトは、単にFromトレイトの逆の働きをします。もし自作の型にFromトレイトが実装されていたら、Intoは必要に応じてそれを呼び出します。

Intoトレイトを使用すると、ほとんどの場合、コンパイラが型を決定することができないため、変換する型を指定する必要があります。しかし、この機能を無料で得られることを考えれば、これは小さなトレードオフです。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

TryFromおよびTryInto

FromおよびIntoと同様に、TryFromおよびTryIntoも型変換を行うジェネリックなトレイトです。From/Intoと異なり、TryFrom/TryIntoトレイトは失敗する可能性のある型変換に用いられるので、Resultを返します。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Stringとの型変換

Stringへの型変換

任意の型をStringに変換するのは簡単で、その型にToStringトレイトを実装するだけです。これを直接実装するよりも、fmt::Displayトレイトを実装するのがよいでしょう。そうすることで自動的にToStringが提供されるだけでなく、print!の章で説明したように、その型を表示できるようにもなります。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Stringの解析

文字列からの型変換において、数値への型変換はよく行われるものの一つです。これを行うイディオムはparse関数を使用することですが、このときに型を推論できるようにするか、もしくは turbofish構文を使用して型を指定するかのいずれかを行います。以下の例では、どちらの方法も紹介しています。

parse関数は、指定された型にFromStrトレイトが実装されていれば、文字列をその型に変換します。このトレイトは標準ライブラリの多くの型に対して実装されています。ユーザー定義の型でこの機能を利用するには、その型に対してFromStrトレイトを実装するだけです。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Rustのプログラムは(ほとんどの場合)文(statement)の連続でできています

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

宣言文にはいくつかの種類があります。最も一般的なのは変数の束縛(variable binding)と;付きの式(expression)です

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

コードブロックも式の一種です。よってブロックを丸ごと値として扱うことができます。その場合ブロック内の最後の式が場所を表す式(例えばローカル変数)に代入されます。ただし、ブロック内の最後の式が;で終わる場合は返り値は()になります。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

条件分岐

処理の流れをコントロールすることはあらゆるプログラミング言語において重要な要素です。

if/else, for等です。Rustの文法を見ていきましょう。

if/else

if-elseを用いた条件分岐は他の言語に似ています。多くの言語では条件式の中を括弧でくくる必要がありますが、Rustではその必要はありません。条件式の直後にはブロックが続きます。if-elseは式の一種で、いずれの分岐先でも返り値の型は同一でなくてはなりません。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

loop

Rustにはloopというキーワードが存在します。これは無限ループを作成するのに使用します。

訳注: while Trueと同じですが、ループのたびに条件を確認しないため、若干高速になります。

ループから抜けだす時はbreak, 即座に次のループに移るときはcontinueが使用できます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

ネストとラベル

ネストしたループを回している時に外側のループをbreakまたはcontinueしたい場合があります。こういった場合にはlabelを用いてループにラベルを貼り、break/continueにそのラベルを渡します。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

loopが返す値

loopの用途のひとつに「成功するまである処理を再試行する」ことがあります。もしその処理が値を返すならば、それをコードの他の部分に渡す必要があるでしょう。breakの後に値を置くと、それがloop式の値として返されます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

while

whileキーワードは条件が真である限り実行され続けるループのために使用します。

悪名高いFizzBuzz問題whileを用いて解いてみましょう。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

forループ

for と range

for in文を用いることで、イテレータ(Iterator)のそれぞれの要素に対して処理をすることが可能です。イテレータを作る最も単純な方法はa..bのような書き方をすることです。これは「a」から「bのひとつ前」までの要素を順に産出(yield)するというものです。

ではforwhileを用いてFizzBuzzを書いてみましょう。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

上記の代わりにa..=bを用いると、両端の値を含む範囲を指定できます。上記の例は次のように書けます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

forとイテレータ

for in構文はIteratorとさまざまな方法でやり取りできます。Iteratorトレイトの章で説明したように、デフォルトではforループにおいてinto_iter関数がコレクションに対して適用されます。しかし、コレクションをイテレータに変換する方法はこれだけではありません。

into_iteriteriter_mutはいずれもコレクションのイテレータへの変換を行いますが、データの「見せ方」の違いにより、そのやり方はそれぞれ異なります。

  • iter - この関数は、各周回においてコレクションの要素を借用します。よってコレクションには手を加えないので、ループの実行後もコレクションを再利用できます。
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  • into_iter - この関数はコレクションからデータを取り出すので、各周回において要素のデータそのものが提供されます。データを取り出してしまうと、データはループ内に「移動」してしまうので、ループ実行後にコレクションを再利用することはできません。
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  • iter_mut - この関数はコレクションの各要素をミュータブル(変更可能)で借用するので、コレクションの要素をその場で変更できます。
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

上記に示した3つのコードにおいて、matchの選択肢の型の違いに注意してください。ここがそれぞれの方法の違いを生む鍵になっています。型が異なれば、当然ながらそれに対して行える処理も変わります。

参照

イテレータ

match

Rustはmatchを用いて、C言語におけるswitchのようなパターンマッチングを行うことができます。 マッチする最初のアームが評価され、取りうるすべての値はカバーされていなければなりません。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

デストラクト

matchは値をさまざまなやり方でデストラクトすることができます。

タプル

以下のように、タプルはmatchを用いてデストラクトすることができます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

タプル

配列とスライス

タプル同様、配列とスライスも以下のようにデストラクトできます:

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

配列とスライス、@マークについてはバインディング

列挙型

列挙型(enum)も似たやり方でデストラクトすることができます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

#[allow(...)], カラーモデル, 列挙型

ポインタとref

Rustのポインタは、C/C++のポインタとは異なる概念なので、デストラクトとデリファレンスを同じようなやり方で扱うことはできない

  • デリファレンスには*を用いる。
  • デストラクトには&, ref, ref mutを用いる。
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

See also:

The ref pattern

構造体

以下のようにして、構造体(struct)も同様にデストラクトすることができる。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

構造体

ガード

match内の条件文をフィルタリングするために、 ガード(guard) を使用することができます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

コンパイラは、match式ですべてのパターンがカバーされているかどうかを調べるときに、 ガード条件を考慮しない点に注意してください。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

タプル

バインディング

いくつかの変数をまとめてマッチ対象とした場合、そのうちの一つを分岐先で使用することはそのままでは不可能です。match内では@マークを使用して変数をバインディングすることができます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Optionのような、enumの値をデストラクトするためにも、バインディングを利用できます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

関数, enums, Option

if let

列挙型をマッチさせるとき、場合によってはmatchを使用すると不自然な書き方になってしまう場合があります。例えば...

#![allow(unused)] fn main() { // Make `optional` of type `Option<i32>` // `optional`という変数の型を`Option<i32>`に指定 let optional = Some(7); match optional { Some(i) => { println!("This is a really long string and `{:?}`", i); // ^ Needed 2 indentations just so we could destructure // `i` from the option. // ^ `i`をoption型からデストラクトするためだけに // インデントが一つ増えてしまっている。 }, _ => {}, // ^ Required because `match` is exhaustive. Doesn't it seem // like wasted space? // ^ `match`は全ての型に対して網羅的でなくてはならないので必要。 // 冗長に見えませんか? }; }

この場合はif letを用いたほうが美しく、失敗時の処理も柔軟に行うことができます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

同じように、if letを列挙型の値にマッチさせるのに利用できます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

if letを利用する別の利点は、パラメータ化されていない列挙型の値をマッチさせられることです。 これは、列挙型がPartialEqを実装もderiveもしていない場合でも同様です。 PartialEqがない場合には、if Foo::Bar == aはコンパイルできません。 列挙型のインスタンスは比較できませんが、if letを使えば動作します。

次の例をif letを利用して修正するのにチャレンジしてみましょう。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

列挙型, オプション, RFC

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:

#![allow(unused)] fn main() { use std::str::FromStr; fn get_count_item(s: &str) -> (u64, &str) { let mut it = s.split(' '); let (count_str, item) = match (it.next(), it.next()) { (Some(count_str), Some(item)) => (count_str, item), _ => panic!("Can't segment count item pair: '{s}'"), }; let count = if let Ok(count) = u64::from_str(count_str) { count } else { panic!("Can't parse integer: '{count_str}'"); }; (count, item) } assert_eq!(get_count_item("3 chairs"), (3, "chairs")); }

See also:

option, match, if let and the let-else RFC.

while let

if letと同様に、while letも不格好なmatch処理を多少マシにしてくれます。例えば、以下のiをインクリメントする処理を見てください。

#![allow(unused)] fn main() { // Make `optional` of type `Option<i32>` // `Option<i32>`の`optional`を作成 let mut optional = Some(0); // Repeatedly try this test. // 変数の照合を繰り返し行う。 loop { match optional { // If `optional` destructures, evaluate the block. // もし`optional`のデストラクトに成功した場合、値に応じて処理を分岐 Some(i) => { if i > 9 { println!("Greater than 9, quit!"); optional = None; } else { println!("`i` is `{:?}`. Try again.", i); optional = Some(i + 1); } // ^ Requires 3 indentations! // ^ 3つものインデントが必要。 }, // Quit the loop when the destructure fails: // デストラクトに失敗した場合、ループを脱出 _ => { break; } // ^ Why should this be required? There must be a better way! // どうしてこんな行を書く必要が?もっと良い方法があるはずです! } } }

while letの使用によってベターになります。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

列挙型(enum), Option, RFC

関数

関数はfnキーワードを用いて定義することができます。引数は変数と同様に型を指定する必要があり、もし関数が値を返すならば->の後にその型も指定する必要があります。

関数内の最後の式が返り値となります。関数の途中で値を返したい場合はreturn文を使用します。loopの最中やif文の中からも値を返すことができます。

では、もう一度FizzBuzz問題を解く関数を書いてみましょう!

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

関連関数とメソッド

関数には特定の型に紐づいたものがあります。これには関連関数とメソッドの2つの形式があります。 メソッドは特定のインスタンスに関連付けて呼ばれる関数であるのに対し、関連関数は型全体に対して定義される関数です。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

クロージャ

Rustにおけるクロージャは、その外側の環境を捕捉した関数のことです。例えば、次のコードは変数xを捕捉したクロージャです。

|val| val + x

クロージャの構文や機能は、その場限りの用途で何かを作るのに便利です。クロージャの呼び出しは関数の呼び出しと全く同じです。しかし、入力の型と戻り値の型は推論させることができますが、入力変数の名前は必ず指定しなくてはなりません。

クロージャの他の特徴を以下に示します。

  • 入力変数を囲むのに、()の代わりに||を用います。
  • 本体が単一の式の場合は、本体の区切り文字({})を省略できます。(それ以外の場合は必須です)
  • 外側の環境にある変数を捕捉することができます。
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

要素の捕捉

クロージャはとてもフレキシブルに動作するように出来ています。クロージャにおいて型アノテーションをする必要が無いのは前述の仕組みのためですが、この仕組みのおかげでユースケースに応じて参照を取得したり値そのものを取得したりといった動作が可能になります。 クロージャは外側の環境にある要素を、以下の形で取得することができます。

  • リファレンス: &T
  • ミュータブルなリファレンス: &mut T
  • 値そのもの: T

クロージャは出来る限りリファレンスを取得しようとし、その他2つは必要なときのみ取得します。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

バーティカルパイプ(訳注:縦線記号||)の前にmoveを使用することで、キャプチャする変数の所有権を取ることをクロージャに強制します。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

Box and std::mem::drop

捕捉時の型推論

Rustはたいていの場合、型アノテーションなしでも変数を捕捉する方法を臨機応変に選択してくれますが、関数を書く場合にはこの曖昧さは許されません。 引数のパラメータとしてクロージャを取る場合、そのクロージャの完全な型はいくつかのtraitsの中の1つを使って明示されなければなりません。 どれが使われるかは、捕捉された値でクロージャが何をするかによって決まります。 制限の少ない順に並べると、下記の通りです。

  • Fn: 参照(&T)によって捕捉するクロージャ
  • FnMut: ミュータブルな参照(&mut T)によって捕捉するクロージャ
  • FnOnce: 値(T)によって捕捉するクロージャ

変数ごとに、コンパイラは可能な限り制約の少ない方法でその変数を捕捉します。

例えば、FnOnceというアノテーションの付けられたパラメータを考えてみましょう。 これはそのクロージャが&T&mut TもしくはTどれか で捕捉することを指定するものですが、コンパイラは捕捉した変数がそのクロージャの中でどのように使用されるかに基づき、最終的に捕捉する方法を選択することになります。

これは、もし移動が可能であれば、いずれの種類の借用であっても同様に可能だからです。 その逆は正しくないことに注意してください。パラメータがFnとしてアノテーションされている場合、変数を&mut TTで捕捉することは許可されません。 しかし&Tは許可されます。

以下の例では、FnFnMut、およびFnOnceを入れ替えて、何が起こるのかを見てみましょう。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

std::mem::drop, Fn, FnMut, Generics, where and FnOnce

クロージャを受け取る関数

クロージャが周辺の環境から変数を取得するやり方は非常に明瞭です。何か注意すべき点はあるのでしょうか? もちろんです。関数内でクロージャを使う場合、[ジェネリック]型を使用する必要があります。詳しく見ていきましょう。

#![allow(unused)] fn main() { // `F` must be generic. // `F` はジェネリック型でなくてはならない fn apply<F>(f: F) where F: FnOnce() { f(); } }

クロージャが定義されると、コンパイラは裏側で、無名の構造体を作り、そこにクロージャによって使用される外側の変数を入れます。同時にFnFnMutFnOnceという名のトレイトのいずれか一つを介してこの構造体に関数としての機能を実装し、実際に呼び出されるまで待ちます。

この無名構造体は型が未指定(unknown)なため、関数を実行する際にはジェネリクスが必要とされます。とはいえ、<T>で指定するだけでは、まだ曖昧です。(訳注: &self&mut selfselfのいずれをとるのかがわからないため)そのため、FnFnMutFnOnceのいずれか一つを実装することで対応しています。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

A thorough analysis, Fn, FnMut, and FnOnce

関数を受け取る関数

これまで、クロージャを引数として渡せることを見てきました。すると次の疑問が浮かんできます

「クロージャではない普通の関数を引数として渡すことは可能なのだろうか?」

可能です!もしパラメータとしてクロージャを取る関数を定義すれば、そのクロージャのトレイト境界を満たす任意の関数をパラメータとして渡すことができます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

クロージャによる変数の捕捉がどのように行われているかを詳しく見たいときはFnFnMutFnOnceを参照してください。

参照

Fn, FnMut, and FnOnce

クロージャを返す関数

クロージャを引数のパラメータとして用いることができるのと同様に、クロージャを戻り値として返すことも可能です。しかし無名のクロージャの型はその定義上、不明であるため、クロージャを返すためにはimpl Traitを使用する必要があります。

クロージャを返すために有効なトレイトは下記の通りです。

  • Fn
  • FnMut
  • FnOnce

更に、moveというキーワードを使用し、全ての捕捉が値でおこなわれることを明示しなければなりません。 これは、関数を抜けると同時に参照による捕捉がドロップされ、無効な参照がクロージャに残ってしまうのを防ぐためです。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

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; }
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

std::iter::Iterator::any

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; }
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Iterator::findは要素への参照を返します。 要素の インデックス を使用したい場合、Iterator::positionを使用してください。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

std::iter::Iterator::find

std::iter::Iterator::find_map

std::iter::Iterator::position

std::iter::Iterator::rposition

高階関数

Rustには高階関数(Higher Order Functions, HOF)を扱う機能が備わっています。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

オプション型イテレータには高階関数が使用されています。

発散する関数

発散する関数 (Diverging functions) は決してリターンしない関数です。こうした関数は ! を使って、空の型であることが示されます。

#![allow(unused)] fn main() { fn foo() -> ! { // この呼び出しは決してリターンしない。 panic!("This call never returns."); } }

他の全ての型と異なり、この型はインスタンス化できません。 この型が持ちうる全ての値の集合は空です。 この型は()型とは異なることに注意してください。 ()型は値をただ1つだけ持つ型です。

例えば、この関数は通常どおりリターンしますが、戻り値には何の情報も含みません。

fn some_fn() { () } fn main() { let _a: () = some_fn(); // この関数はリターンするので、この行は実行される。 println!("This function returns and you can see this line."); }

一方、この関数は呼び出し元に決してリターンしません。

#![feature(never_type)] fn main() { // この呼び出しは決してリターンしない。 let x: ! = panic!("This call never returns."); // この行は決して実行されない。 println!("You will never see this line!"); }

これは抽象的な概念に見えるかもしれませんが、実際のところはとても実用的で、便利なことも多いのです。 この型の主な利点は、他のどのような型にもキャストできることです。 そのため、例えば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で修飾することでパブリックな属性にすることができます。パブリックな属性のみがモジュールの外のスコープからアクセスすることができるようになります。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

構造体の場合

構造体はそれ自身に加え、フィールドごとにもパブリック・プライベートを設定することができます。デフォルトではプライベートですが、pub宣言をすることで、フィールドをパブリックにすることができます。これは、構造体がモジュールの外から参照される時に限り意味のあるもので、情報の隠蔽(カプセル化)を達成するための機能です。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

ジェネリック型, メソッド

use宣言

use宣言をすることで、要素の絶対パスを新しい名前にバインドすることができ、より簡潔な記述が可能になります。例えば以下のように使えます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

asキーワードを使用することで、インポートを別名にバインドすることができます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

superself

super及びselfキーワードは、要素にアクセスする際に、曖昧さをなくし、不必要なハードコーディングを避けるために使用できます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

ファイルの階層構造

モジュールはファイル・ディレクトリ間の階層構造と対応関係にあります。モジュールにお互いがどのように見えているか、以下の様なファイルを例に詳しく見ていきましょう。

$ 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.rsmod宣言を含んでいるのならば、コンパイルの 前に 、そのモジュールファイルの中身が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.tomlformat 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つのファイルに出力するケースがあります。

#![allow(unused)] fn main() { #[cfg(test)] mod tests { // Import the necessary modules // 必要なモジュールをインポートする。 use std::fs::OpenOptions; use std::io::Write; // This test writes to a file // ファイルに書き込むテスト。 #[test] fn test_file() { // Opens the file ferris.txt or creates one if it doesn't exist. // ferris.txtというファイルを開くか、存在しない場合は作成する。 let mut file = OpenOptions::new() .append(true) .create(true) .open("ferris.txt") .expect("Failed to open ferris.txt"); // Print "Ferris" 5 times. // "Ferris"と5回書き込む。 for _ in 0..5 { file.write_all("Ferris\n".as_bytes()) .expect("Could not write to ferris.txt"); } } // This test tries to write to the same file // 同じファイルに書き込むテスト。 #[test] fn test_file_also() { // Opens the file ferris.txt or creates one if it doesn't exist. // ferris.txtというファイルを開くか、存在しない場合は作成する。 let mut file = OpenOptions::new() .append(true) .create(true) .open("ferris.txt") .expect("Failed to open ferris.txt"); // Print "Corro" 5 times. // "Corro"と5回書き込む。 for _ in 0..5 { file.write_all("Corro\n".as_bytes()) .expect("Could not write to ferris.txt"); } } } }

以下のような結果を得ようと意図しています。

$ cat ferris.txt Ferris Ferris Ferris Ferris Ferris Corro Corro Corro Corro Corro

しかし、実際にferris.txtに出力されるのは、以下の通りです。

$ cargo test test_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と呼ばれるリント機能を持つため、使用されていない関数が存在するときに警告を出します。 アトリビュート によってこの機能を無効化することができます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

実際のコード中では、使用されていないコードが有る場合はそれを除外するべきです。この文書中では随所でアトリビュートによって警告を抑制していますが、それはあくまでインタラクティブな例を皆さんに提供するためです。

クレート

crate_typeアトリビュートは、そのクレートがライブラリ、バイナリのいずれにコンパイルされるべきかをコンパイラに伝えるために使用します。ライブラリの場合は、どのタイプのライブラリであるかも伝えることができます。crate_nameはクレートの名前を決定するのに使用します。

しかし、crate_typeアトリビュートもcrate_nameアトリビュートも、RustのパッケージマネージャCargoを利用している場合は何の影響もないと知っておくことは重要です。Cargoは大半のRustプロジェクトで利用されており、実世界でのcrate_typecrate_nameの利用は比較的限られています。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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式の中のすべてのブロックが有効でなくてはなりません。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

参照(reference), cfg!, マクロ.

条件の追加

target_osのように、いくつかの条件分岐はrustcが暗黙のうちに提供しています。条件を独自に追加する場合には--cfgフラグを用いてrustcに伝える必要があります。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

独自の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という構造体がそれ以前に定義されていても同様です。

では、手を動かしながらジェネリック型の構文を体験していきましょう。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

構造体

関数

「型Tはその前に<T>があるとジェネリック型になる」というルールは関数に対しても当てはまります。

ジェネリック関数を使用する際、以下の様な場合には型パラメータを明示する必要があります。

  • 返り値がジェネリック型である場合。
  • コンパイラが型パラメータを推論するのに十分な情報がない場合

型パラメータを明示したうえでの関数呼び出しの構文はfun::<A, B, ...>()のようになります。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

関数, 構造体

メソッド

関数と同様、implでメソッドを実装する際にもジェネリック型特有の記法が必要です。

#![allow(unused)] fn main() { struct S; // Concrete type `S` // 具象型`S` struct GenericVal<T>(T); // Generic type `GenericVal` // ジェネリック型`GenericVal` // impl of GenericVal where we explicitly specify type parameters: // 型パラメータを指定したうえで、GenericValにメソッドを実装 impl GenericVal<f32> {} // Specify `f32` // `f32`の場合のメソッド impl GenericVal<S> {} // Specify `S` as defined above // 上で定義した`S`への実装 // `<T>` Must precede the type to remain generic // ジェネリック型のまま扱うには`<T>`が先に来る必要がある。 impl<T> GenericVal<T> {} }
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

参照を返す関数, impl, struct

ジェネリックトレイト

もちろんトレイトもジェネリクスを活用することができます。ここではDropトレイトをジェネリックメソッドとして再実装し、自身と引数として受け取った値の両方をdropするようなメソッドにします。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

Drop, 構造体(struct), トレイト(trait)

ジェネリック境界

ジェネリックプログラミングをしていると、型パラメータが特定の機能を持っていることを規定するため、トレイトに境界(bound)を設ける必要があることがよくあります。例えば、以下の例では、引数のDisplayトレイトを用いてプリントを行うため、TDisplayを持っていることを規定しています。つまり、「TDisplayを実装 していなくてはならない 」という意味です。

// 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]);

境界のもう一つの効果は、ジェネリック型のインスタンスが、境界条件となるトレイトのメソッドにアクセスすることができるようになる点です。以下がその例です。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

付け加えておくと、where句を用いて境界を適用することもできます。場合によってはこちらの記法を使用したほうが読みやすくなる場合もあります。

参照

std::fmt, 構造体(struct), トレイト

テストケース: 空トレイト

トレイト境界の仕組みから、「トレイトがなにも機能を持っていなくとも境界条件として使用できることには変わりはない」という帰結がもたらされます。EqCopystdライブラリにおけるそのような例です。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

std::cmp::Eq, std::marker::Copy, トレイト

複数のジェネリック境界

+を用いて1つの型に複数のトレイト境界を設けることができます。複数の引数を受け取るときは、通常時と同様、,で区切ります。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

std::fmt, トレイト

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句の方が通常の構文より表現力が高い場合

があります。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

RFC, 構造体, トレイト, エラーハンドリングの日本語による解説記事

ニュータイプイディオム

既に定義済みの型であってもstructを使うことであたかも別の型newtypeであるかのように定義することが可能です。なお、それらが正しい型としてプログラムに供給されているか否かは、コンパイル時に保証されます。

例えば、年齢を年単位で確認するold_enoughには「Years」という型の値を 与えなければならない ようにすることが可能です。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

最後の print文 のコメントを外して、与えられた型が Years でなければならないことを確認してください。

newtypeの元に使われている型のデータを取得するには、以下のようにタプルや分配構文を用いることで取得できます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

structs

関連型

関連要素(Associated Items)とは複数の型の要素(items)に関係のある規則の総称です。トレイトの拡張機能であり、トレイトの中で新しい要素を定義することを可能にします。

そのように定義する要素の一つに 関連型 があります。これにより、ジェネリックなコンテナ型に対するトレイトを使用する際に、よりシンプルな書き方ができるようになります。

参照

RFC

関連型が必要になる状況

コンテナ型に、その要素に対してジェネリックなトレイトを実装した場合、そのトレイトを使用する者は全てのジェネリック型を明記 しなくてはなりません

以下の例ではContainsトレイトはジェネリック型ABの使用を許しています。その後、Container型に対してContainsを実装していますが、その際後にfn difference()が使用できるように、ABはそれぞれi32と明記されています。

Containsはジェネリックトレイトなので、fn difference()では 全ての ジェネリック型を宣言しなくてはなりません。実際のところ、AB引数 であるCによって決定されていて欲しいにも関わらず、です。これは次のページで紹介する関連型と呼ばれる機能によって可能です。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

構造体, トレイト

関連型

関連型を使用すると、コンテナ型の中の要素をトレイトの中に 出力型 として書くことで、全体の可読性を上げることができます。トレイトを定義する際の構文は以下のようになります。

#![allow(unused)] fn main() { // `A` and `B` are defined in the trait via the `type` keyword. // (Note: `type` in this context is different from `type` when used for // aliases). // `A`と`B`は`type`キーワードを用いてトレイト内で宣言されている。 // (注意: この文脈で使用する`type`は型エイリアスを宣言する際の`type`とは // 異なることに注意しましょう。) trait Contains { type A; type B; // Updated syntax to refer to these new types generically. // これらの新しい型をジェネリックに使用するために、構文が // アップデートされています。 fn contains(&self, _: &Self::A, _: &Self::B) -> bool; } }

Containsトレイトを使用する関数において、ABを明示する必要がなくなっていることに注目しましょう。

// 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 { ... }

前セクションの例を関連型を使用して書きなおしてみましょう。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

幽霊型パラメータ

幽霊型(Phantom Type)とは実行時には存在しないけれども、コンパイル時に静的に型チェックされるような型のことです。

構造体などのデータ型は、ジェネリック型パラメータを一つ余分に持ち、それをマーカーとして使ったりコンパイル時の型検査に使ったりすることができます。このマーカーは実際の値を何も持たず、したがって実行時の挙動そのものにはいかなる影響ももたらしません。

以下の例では、そのようなマーカーとして幽霊型(std::marker::PhantomData)を用い、それぞれ異なった型の値を持つタプルを作成します。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

導出(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>; ... }

以下は全体を示した例です。:

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

借用(&), トレイトバウンド, 列挙型, impl & self, 演算子のオーバーロード, 参照, トレイト (X for Y), タプル.

スコーピングの規則

所有権、借用、ライフタイムといったRustに特有の概念において、変数のスコープは重要な役割を果たします。すなわち、スコープの存在によってコンパイラは借用は可能か否か、メモリ上の資源は解放可能か、変数はいつ作成され、いつ破棄されるか。といったことを知るのです。

RAII

Rustの変数は単にデータをスタック上に保持するだけのものではありません。例えばヒープメモリを確保するBox<T>のように、変数はメモリ上の資源を 保有 する場合もあるのです。RustはRAII(Resource Acquisition Is Initialization)を強制するので、オブジェクトがスコープを抜けると、必ずデストラクタが呼び出されてそのオブジェクトが保持していた資源が解放されます。

この振る舞いは リソースリーク (resource leak)バグを防ぐのに役立ちます。手動でメモリを解放したり、メモリリークバグにわずらわされたりすることはなくなるのです!簡単な例で説明しましょう。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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関数内の変数がスコープを抜けるときにカスタムデストラクタが呼び出されるはずです。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

ボックス

所有権とムーブ

変数には自身の保持する資源を開放する責任があるため、 資源は一度に一つの所有者 しか持つことができません。これはまた、資源を2度以上開放することができないということでもあります。ここで、全ての変数が資源を所有するわけではないということに注意しましょう。(e.g. 参照

変数をアサインする(let x = y)際や、関数に引数を値渡しする(foo(x))際は、資源の 所有権(ownership) が移動します。Rustっぽく言うと、「 ムーブ 」です。

資源を移動すると、それまでの所有者(訳注: 変数などのこと)を使用することはできなくなります。これによりダングリングポインタの発生を防げます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

ミュータビリティ

データのミュータビリティは所有権を移譲した際に変更できます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

部分的ムーブ

1つの変数の デストラクト の中で、 ムーブ参照 の両方のパターンバインディングを同時に使用することができます。両方を使用すると、変数の一部がムーブされ、他の部分が参照として残るという変数の部分的ムーブが発生した状態になります。変数の部分的ムーブが発生すると親変数はその後使用できませんが、参照されているだけの部分(ムーブされていない部分)は使用することができます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

この例では、age変数をヒープ上に保持し、部分的ムーブを説明しています。 上記コードでrefを削除すると、person.ageの所有権がage変数にムーブされるため、エラーになります。 もしもPerson.ageがスタック上に保持されていたら、 ageの定義がperson.ageをムーブすることなくデータをコピーするので、 refは必須ではないのですが、実際にはヒープ上に保持されているためrefは必須です。

参照

デストラクト

借用

実際には、データの所有権を完全に受け渡すことなく一時的にアクセスしたいという場合がほとんどです。そのために、Rustでは 借用(borrowing) という仕組みを用います。値そのもの(T)を受け渡すのではなく、そのリファレンス(&T)を渡すのです。

コンパイラは借用チェッカを用いてリファレンスが 常に 有効なオブジェクトへの参照であることを、コンパイル時に保証します。つまり、あるオブジェクトへのリファレンスが存在しているならば、そのオブジェクトを破壊することはできないということです。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

ミュータビリティ

ミュータブルなデータは&mut Tでミュータブルに(変更可能な形で)借用することができます。これは ミュータブルな参照 と呼ばれ、読み込み・書き込みの権限を借用者に与えます。対照的に&Tはデータをイミュータブルな参照を介して借用し、借用した側はデータを読み込みはできますが書き込みはできません。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

static

エイリアス

データは一度にいくつでもイミュータブルに借用することができますが、その間オリジナルのデータをミュータブルに借用することはできません。一方でミュータブルな借用は一度に 一つ しか借用することができません。オリジナルのデータをもう一度借用できるのはミュータブルな参照が最後に使われた場所より あとで なければいけません。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

refパターン

letを介してデストラクトやパターンマッチングを行う場合、refキーワードを用いて構造体・タプルのフィールドへのリファレンスを取得することができます。以下の例ではこれが有用になる例を幾つかお見せします。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

ライフタイム

ライフタイム はコンパイラ(借用チェッカーと呼ばれる場合もあります)が、全ての借用に問題がないことを確認するために使用する仕組みです。正確にいうと、変数のライフタイムは作成時に開始し、破棄された時に終了します。ライフタイムとスコープは同時に語られることが多いですが、同じものではありません。

例として&を用いて変数を借用する場合をあげましょう。借用のライフタイムは宣言時に決定し、そこから貸し手が破棄されるまで続きます。しかしながら、借用のスコープは参照が使われる際に決定します。

以下の例からこのセクションの残りでは、ライフタイムとスコープの関係、そしてそれらがいかに異なるものであるかを見ていきます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

ここで、一切の名前や型がライフタイムにアサインされていないことに注意しましょう。これにより、ライフタイムの使われ方がこれから見ていくようなやり方に制限されます。

明示的アノテーション

借用チェッカーは参照がどれだけの間有効かを決定するために、明示的なアノテーションを使用します。ライフタイムが省略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いずれよりも 長くなってはなりません。

以下はライフタイムを明示的に書く場合の例です。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1

省略 はライフタイムが暗黙のうちに(プログラマから見えない形で)アノテートされることを指します。

参照

ジェネリクス, クロージャ

関数

省略をしない場合、ライフタイムのシグネチャ(e.g. <'a>)を持つ関数にはいくつかの制限があります。

  • 全ての変数においてライフタイムを明示しなくてはならない。
  • 返り値となる参照はすべて引数と同じライフタイムか、staticライフタイムを持たなくてはならない

加えて、引数のない関数から参照を返す場合、それが結果的に無効なデータへの参照になるならば、禁止されている

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

functions

メソッド

メソッドのライフタイムは関数に似ている。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

メソッド

構造体

構造体におけるライフタイムも関数のそれと似ている。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

structs

トレイト

トレイトのメソッドにおけるライフタイムのアノテーションは、 基本的には関数に似ています。 implにもライフタイムのアノテーションがあることに注意してください。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

トレイト

ライフタイム境界

ジェネリック型に境界(bound)を与え、特定のトレイトを実装していることを保証できるのと同様、ライフタイム(それ自身ジェネリック型)にも境界を与えることができます。:は、ここでは多少異なる意味を持ちますが+は同じです。以下の構文の意味をチェックしてください。

  1. T: 'a: T内の 全ての 参照は'aよりも長生きでなくてはならない
  2. T: Trait + 'a: 上に加えてTTraitという名のトレイトを実装してなくてはならない。

上記の構文を実際に動く例で見ていきましょう。whereキーワードの後に注目してください。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

ジェネリクス, ジェネリック境界, 境界が複数の場合

圧縮

長いライフタイムは、短いものに圧縮(coerce)することで、そのままでは動作しないスコープの中でも使用できるようになります。これは、Rustコンパイラが推論の結果として圧縮する場合と、複数のライフタイムを比較して圧縮する場合があります。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

スタティックライフタイム

Rustにはいくつかの予約されたライフタイム名があります。その1つがstaticで、2つの状況で使用することがあります。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

2つの状況におけるstaticは微妙に異なる意味を持っており、Rustを学ぶときの混乱の元になっています。 いくつかの例とともにそれぞれの使い方を見てみましょう。

参照のライフタイム

参照のライフタイムが'staticであることは、参照が指し示す値がプログラムの実行中に渡って生き続けることを示します。 また、より短いライフタイムに圧縮することも可能です。

'staticライフタイムを持つ変数を作るには下記の2つ方法があります。 どちらの場合も、値は読み取り専用のメモリ領域に格納されます。

  • static宣言とともに定数を作成する。
  • 文字列リテラルで&'static str型を持つ変数を作成する。

では、それぞれの方法の例を見ていきましょう。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

トレイト境界

トレイト境界としての'staticは型が非静的な参照を含まないことを意味します。 言い換えると、レシーバはその型をいくらでも長く保持することができ、意図的にドロップするまでは決して無効になることはないということです。

次のポイントを押さえておきましょう。所有権のある値が'staticライフタイム境界をパスするとしても、その値への参照が'staticライフタイム境界をパスするとは限りません。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

コンパイラのメッセージはこのようになります、

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`は借用されたままここでドロップされる

参照

'static 定数

省略

ライフタイムのパターンのうちのいくつかは、他と比べてあまりにも一般的に使用されるため、タイプ量を減らし可読性を上げるために省くことができます。これは省略として知られており、それらのパターンが一般的であるというだけの理由で存在しています。

以下のコードでは省略の例を幾つかお見せします。より完全な説明を見たい場合は、「プログラミング言語Rust」のライフタイムの省略の項を見てください。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

ライフタイムの省略

トレイト

トレイト(trait)とは任意の型となりうるSelfに対して定義されたメソッドの集合のことです。同じトレイト内で宣言されたメソッド同士はお互いにアクセスすることができます。

トレイトはあらゆるデータ型に実装することができます。以下の例ではまずAnimalというメソッドの集合を定義し、その後AnimalトレイトをSheepというデータ型に対して実装します。これによりAnimalのメソッドをSheepが使用することが可能になります。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

導出(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, これは{:?}フォーマッタを利用して値をフォーマットするためのトレイト
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

derive

dynを利用してトレイトを返す

Rustのコンパイラはあらゆる関数のリターン型に必要なスペースを知っておく必要があります。 つまり、すべての関数は具体的な型を返す必要があるのです。 他の言語と違って、Animalのようなトレイトがある場合に、Animalを返す関数を書くことはできません。 なぜなら、そのトレイトの異なる実装はそれぞれ別の量のメモリを必要とするからです。

しかし、簡単な回避策があります。 直接トレイトオブジェクトを返す代わりに、Animal含む Boxを返すのです。 Boxはヒープ中のメモリへの単なる参照です。 参照のサイズは静的に知ることができ、コンパイラは参照がヒープに割り当てられたAnimalを指していると保証できるので、私たちは関数からトレイトを返すことができます。

ヒープにメモリを割り当てる際、Rustは可能な限り明示的であろうとします。 なので、もしあなたの関数がヒープ上のトレイトへのポインタを返す場合、例えばBox<dyn Animal>のように、リターン型にdynキーワードをつける必要があります。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

演算子のオーバーロード

Rustでは、多くの演算子はトレイトによってオーバーロードすることができます。つまり、一部の演算子は引数となる値の型に応じて異なる役割を果たすことができるということです。これが可能なのは、演算子が実際にはメソッド呼び出しの糖衣構文にすぎないからです。例えばa + bにおける+演算子はaddメソッドを(a.add(b)の形で)呼び出します。このaddメソッドはAddトレイトの一部です。それ故、+Addトレイトを実装している全ての型に対して有効なのです。

Addなどの、演算子をオーバーロードするトレイトの一覧はcore::opsにあります。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

See Also

Add, 構文の索引

メモリ解放

Dropトレイトにはメソッドが一つだけしかありません。dropです。これは、オブジェクトがスコープから抜けた時に自動で呼ばれます。Dropトレイトの主な使用目的は、インスタンスが所有する資源を開放することです。

Dropトレイトを実装している型の例としてはBoxVecStringFileProcess等があげられます。Dropトレイトは任意の型に対して手動で実装することができます。

以下の例ではdropメソッドにコンソールへの出力を追加することで、dropが呼ばれたタイミングが分かるようにしています。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

イテレータ

Iteratorトレイトは、例えば配列のような、要素の集合に対してイテレータを実装するためのトレイトです。

このトレイトはnextの要素に相当するものを決定するためのメソッドのみを要求します。このメソッドはimplブロック内で手動で実装するか、あるいは(配列やrangeのように)自動で定義されます。

サッとイテレータを使いたい時は、for文で集合からイテレータを作成することが良くあります。これは.into_iter()メソッドを呼び出しています。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

impl Trait

impl Traitは2つの利用方法があります:

  1. 引数の型
  2. リターン型

引数の型

あなたの関数がジェネリックなトレイトを使用していて、特定の型を意識していない場合、impl Traitを引数の型として利用して、関数宣言をシンプルにできます。

例えば、次のコードを考えてみましょう:

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

parse_csv_documentはジェネリックなので、BufReadを実装する任意の型を取ることができます。 例えば、BufReader<File>[u8]です。 Rがどんな型かは重要ではなく、srcの型宣言に使われているだけなので、この関数は以下のように書くこともできます:

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

impl Traitを引数の型として利用するということは、どのような形式の関数であるか明示できないので、注意してください。 例えば、parse_csv_document::<std::io::Empty>(std::io::empty())は2番目の例では動作しません。

リターン型

あなたの関数がMyTraitを実装する型を返す場合、 リターン型を-> impl MyTraitのように書けます。 これで型シグネチャをとてもシンプルにできます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

より重要なことに、Rustの型には書き表せないものがあるのです。 例えば、あらゆるクロージャは独自の無名な具象型を持ちます。 impl Trait構文がない時は、クロージャを返すにはヒープ上に置かねばなりませんでした。 しかし今では次のようにすべて静的に行えます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

impl Traitを使って、mapfilterクロージャを使うイテレータを返すこともできます。 おかげでmapfilterを簡単に使えます。 クロージャ型は名前を持たないので、あなたの関数がクロージャを持つイテレータを返す場合、 明示的なリターン型を書くことはできません。 しかしimpl Traitを使うことで簡単にできます:

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

クローン

メモリ上の資源を扱う際、変数束縛や関数呼び出しを介して移動させるのがデフォルトの挙動です。しかしながら、場合によっては資源のコピーを作るのが適切なこともあります。

Cloneトレイトはまさにこのためにあります。普通はCloneトレイトで定義されている.clone()を用います。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

スーパートレイト

Rustには"継承"はありませんが、あるトレイトを別のトレイトの上位集合として定義できます。 例えば:

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

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.

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

The Rust Programming Language chapter on Fully Qualified syntax

macro_rules!

Rustはメタプログラミングを可能にする、パワフルなマクロシステムを備えています。これまで見てきたように、マクロは!で終わることを除けば関数のように見えます。関数と違うのは関数呼び出し(function call)を生成する代わりに、ソースコード中に展開され、周囲のプログラムとともにコンパイルされる点です。

しかし、Cやその他の言語のマクロが文字列のプリプロセッシングをするのと異なり、Rustのマクロは抽象構文木へと展開されるので、予期せぬprecendece(演算子の優先順位)のバグに出くわすことがありません。

マクロを作成するにはmacro_rules!というマクロを使用します。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

ではどうしてマクロは便利なのでしょうか?

  1. 同じことを繰り返し書いてはいけない (Don't repeat yourself) から。 複数の場所で、別の型だけれど似たような機能が必要な時がよくあります。 しばしば、マクロはコードを繰り返し書くのを避ける有用な手段なのです(あとで詳述)。
  1. ドメイン特化言語であるから。マクロを使うと、特定の目的のための特定の構文を定義することができます(あとで詳述)。
  1. 可変個引数によるインターフェース。 取る引数の数が可変であるようなインターフェースを定義したくなることもあるでしょう。 例えば、println!は、フォーマット文字列に依存した任意の数の引数を取ることができます(あとで詳述)!

構文

以下のサブセクションでは、Rustにおいてマクロを定義する方法を示します。 3つの基本的な考え方があります:

識別子

macroの引数は$が頭につきます。型は 識別子 (designator)でアノテーションされます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

使用できる識別子には以下のようなものがあります。

  • block
  • expr 式に使用
  • ident 関数、変数の名前に使用
  • item
  • literal はリテラル定数(訳注:文字だけではない。Literal expressionsを参照)に使用
  • pat (パターン)
  • path
  • stmt (宣言)
  • tt (トークンツリー)
  • ty ()
  • vis (可視性修飾子)(訳注:pub (crate)とか)

完全なリストを見るには、Rustリファレンスを読んでください。

オーバーロード

マクロは異なる引数の組み合わせを取るようにオーバーロードすることができるため、macro_rules!はマッチと似たような使い方をすることができます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

繰り返し

マクロは引数のリストの中で+を使うことができ、そうすることによって、引数が少なくとも1回以上繰り返されるということを示すことができます。同様に*の場合は、0以上を示します。

以下の例では、マッチ対象を $(...),+で囲むことにより、カンマで区切られた1つ以上の式とマッチします。最後のセミコロンは必須ではないことに注目しましょう。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

DRY (Don't Repeat Yourself)

マクロは関数やテストなどにおいて、共通の部分を抽出することでDRYなコードを書くのに役立ちます。ここではVec<T>+=*=-=を実装、テストするにあたって、マクロがどのように役立つかを見ていきます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
$ 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を定義したいとしましょう。 式を与えると、出力がコンソールに書き出されるようにしたいです。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

出力はこうなります:

1 + 2 = 3 (1 + 2) * (3 / 4) = 0

これはとても単純な例ですが、lazy_staticclapのように、もっと複雑なインターフェースも開発されています。

また、マクロの中に2組の括弧があることにも注目してください。 外側のは、()[]に加え、macro_rules!の構文の一部です。

可変個引数によるインターフェース

可変個引数のインターフェースとは、任意の数の引数を取るものです。 例えば、println!は、フォーマット文字列の定義に従い、任意の数の引数を取ることができます。

前のセクションのcalculate!マクロを、可変個引数に拡張することができます:

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

出力:

1 + 2 = 3 3 + 4 = 7 (2 * 3) + 1 = 7

エラーハンドリング

エラーハンドリングとは失敗の起きる可能性を扱うプロセスのことです。例えば、ファイルを読み込むのに失敗した際、その 誤った インプットを使い続けるのは明らかに問題です。そのようなエラーを通知して明示的に扱うことで、残りのプログラムに問題が波及することを防ぐことができるようになります。

Rustには、これからこの章で見ていく通り、エラーを処理するための様々な方法が存在します。それらは全て僅かに異なり、ユースケースも異なります。経験則として:

明示的なpanicはテストや復旧不可能なエラーに対して効果的です。プロトタイプにも便利で、例えば未実装の関数を扱う時などに有効ですが、このような場合にはより叙述的なunimplementedの方が良いでしょう。テストにおいてはpanicは明示的にテストを失敗させるための良い手法になるでしょう。

Option型は値があるとは限らない場合や、値が無いことがエラーの条件とならない場合に有効です。例えば親ディレクトリ(/C:はそれを持ちません)などです。Optionを扱う際は、unwrapがプロトタイプや値が確実に存在することが約束されるケースに使えます。しかし、expectの方が何かが上手くいかなかった際にエラーメッセージを指定することができるため、より便利でしょう。

何かが上手くいかない可能性があったり、呼び出し元が問題を処理しなければならない時は、Resultを使いましょう。unwrapexpectを実行することもできます(テストや短期的なプロトタイプ以外では使わないでください)。

より詳細なエラーハンドリングに関する議論については、オフィシャルブックの該当の章を参考にしてください。

訳注: こちらのQiitaの日本語記事も参考になります。「RustでOption値やResult値を上手に扱う」

panic

panicは、最もシンプルなエラーハンドリングの仕組みです。エラーメッセージの出力、スタックの巻き戻し、そして多くの場合プログラムの終了を実行します。 例として、エラー条件に対して明示的にpanicを呼び出してみましょう。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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.

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Here is another example focusing on rewriting drink() and explicitly use the unwind keyword.

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

The panic strategy can be set from the command line by using abort or unwind.

rustc lemonade.rs -C panic=abort

Optionunwrap

以前の例では、甘いレモネードを飲んだ際にpanicを呼び出すことによって、自由にプログラムの実行を失敗させられることが分かりました。では、何らかの飲み物を期待しているにもかかわらず、何も受け取らなかったらどうなるでしょう?これは悲惨なケースになるので、エラーハンドリングする必要があります!

このケースに対して、レモネードと同じように、空文字列("")と比較することもできますが、せっかくRustを使っているので、その代わりにコンパイラに飲み物がないケースを指摘させてみましょう。

stdライブラリの中の、Option<T>と呼ばれるenumは、任意の型Tである変数の値が存在しない可能性がある場合に用いられます。値の状態によって、下記2つのパターンのうちの1つとして扱われます。

  • Some(T): 型Tの値がある場合
  • None: 値が存在しない場合。

これらはmatchを用いて明示的に扱うこともできますし、unwrapで暗黙に処理することもできます。後者はSomeの中の値を返すかpanicするかのどちらかです。

expectメソッドを用いて、panicを手動でカスタマイズできることに触れておきましょう。これは(unwrapをそのまま用いた場合よりも)内容が理解しやすいエラーメッセージを出力するのに役立ちます。次の例では、結果をより明示的に、可能ならいつでもpanicできるように扱っていきます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

?によるOptionのアンパック

Optionをアンパックするにはmatch文を使うこともできますが、?を使う方が簡単になることが多いでしょう。Optionxがあるとすると、x?を評価した値は、xSomeの場合はxに格納された値となり、そうでなければ実行中の関数を終了させ、Noneを返します。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

多くの?を共に使うことで、リーダブルなコードを書くことができます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Combinators: map

matchOptionを扱うのに適したメソッドです。しかし、大量にこれを使用しているとじきに億劫になってくるでしょう。引数の値が有効である(訳注: この場合はNoneでない)必要がある関数を扱う際には特にそうです。 そうした場合には、コンビネータを使うと、処理の流れをモジュール化されたやり方で管理できます。

Some -> SomeあるいはNone -> Noneの単純な操作を適用する必要がある場合には、Optionmap()というビルトインのメソッドを提供していますので、これを使用しましょう。 map()のフレキシビリティは、複数のmap()をチェインしなければならない場合にさらに際立ちます。

以下の例では、process()が直前の関数全てを用いた場合と同じ機能を、よりコンパクトに果たしているのがわかります。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

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()には不適切な型です。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

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.

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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:

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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:

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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:

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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をもたらします。エラーハンドリングでは、ResultOptionで重複するコンビネータが多くあります。

Rustを書いていく中で、parse()メソッドなど、Result型を返すメソッドを目にするでしょう。文字列を他の型にパースすることは必ずしも成功する訳ではないため、Resultを返すことで失敗するケースについてもカバーできるのです。

早速、文字列をparse()した場合の成功例と失敗例を見てみましょう。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

失敗例では、parse()がエラーを返すためunwrap()がパニックします。そして、panicはプログラムを終了させて不快なエラーメッセージを出力します。

エラーメッセージを改善するために、リターン型に対してもっと明確になるべきで、またエラーを明示的に処理することを考えるべきです。

main内で使うResult

Result型は、明示的な指定によりmain関数のリターン型にもなります。一般に、main関数は以下のような形になるでしょう。

fn main() { println!("Hello World!"); }

一方mainResultをリターン型とすることも可能です。エラーがmain関数内で発生した時、エラーコードを返し、エラーに関するデバッグ表記を(Debugトレイトを使って)出力します。以下の例ではそのようなシナリオを示し、この先の節でカバーする内容に触れていきます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Resultmap

前の例で見たmultiplyでのパニックは、コードを強固にするためには書きません。一般に、呼び出した側がエラーをどのように対処するべきかを自由に決められるように、エラーを呼び出した場所に返すのが好ましいです。

まずは、どのようなエラー型を扱っているのかを知る必要があります。Err型を定めるために、i32に対しFromStrトレイトを使って実装されたparse()を見てみましょう。結果、Err型はParseIntErrorというものであることが分かります。

以下の例では、単純なmatch文が全体として扱いづらいコードにしています。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

幸運にも、Optionmapand_then、その他多くのコンビネータもResultのために実装されています。Resultに全てのリストが記載されています。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Resultに対するエイリアス

特定のResult型を何度も使いたくなるのはどんな時でしょう?Rustはエイリアスの作成をサポートしていたことを思い出してください。便利なことに、特定のResult型に対しても定義することができます。

モジュールレベルでは、エイリアスの作成は非常に役に立ちます。特定のモジュールで見られるエラーは同じErr型を持つため、単一のエイリアスで簡潔にResultsに関わる全てを定義できます。stdライブラリが提供するもの(io::Result)もあるほど有益なのです!

簡単な例で構文を見てみましょう。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

io::Result

早期リターン

前の例では、コンビネータの活用によりエラーを明示的に処理しました。場合分けに対する別の対処法として、match文と早期リターンを組み合わせて使うこともできます。

つまり、エラーが発生した時点で関数の実行を止め、エラーを返してしまうという単純な方法が使えるということです。この方法の方がより読みやすく書きやすい場合があります。早期リターンを使って実装された、前の例の新たなバージョンを考えてみましょう。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

ここまでで、コンビネータと早期リターンによる明示的なエラーハンドリングについて学びました。しかし、パニックは一般に避けたいですが、全てのエラーを明示的に処理するのも厄介でしょう。

次の節では、panicを発生させずにunwrapする必要があるケースのための?について紹介していきます。

?の導入

時にはpanicの可能性を無視して、unwrapのシンプルさを活用したいこともあるでしょう。今までのunwrapは、値を取り出すためだけであろうとも、ネストを深く書くことを要求しました。そして、これがまさに?の目的です。

Errを見つけるにあたり、2つのとるべき行動があります。

  1. 可能な限り避けたいと決めたpanic!
  2. Errは処理できないことを意味するためreturn

?ほぼ1まさしく、Errに対してpanicするよりreturnするという点でunwrapと同等です。コンビネータを使った以前の例をどれだけ簡潔に書けるか見てみましょう。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

try!マクロ

?ができる前、同様の動作をtry!マクロによって行うことができました。現在は?オペレータが推奨されていますが、古いコードではtry!に出会うこともあります。try!を使って前の例と同じmultiply関数を実装すると、以下のようになるでしょう。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1

詳細はre-enter ?を参照。

複数のエラー型

Resultが他のResultと連携したり、Optionが他のOptionと連携するなど、今までの例はとても便利な物でした。

時にはOptionResultと連携したり、Result<T, Error1>Result<T, Error2>と連携する必要もあるでしょう。そのような場面では、異なるエラー型を構成しやすく、かつ連携しやすく管理したいです。

以下のコードはunwrapの2つのインスタンスが異なるエラー型を生成します。Vec::firstOptionを返し、一方でparse::<i32>Result<i32, ParseIntError>を返しています。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

この先の節では、これらの問題を処理する方法について見ていきます。

OptionからResultを取り出す

混在するエラー型に対する最も基本的な対処法は、単にお互いを埋め込んでしまうことです。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

中には、Optionの中身がNoneの場合はそのまま処理を進め、エラーの検出に限り実行を止めたいという場合もあるでしょう(?を使った時のように)。いくつかのコンビネータによって簡単にResultOptionをスワップすることができます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

エラー型を定義する

異なるエラー型をマスクし単一のエラー型として扱えるようにすると、コードがシンプルになる場合があります。ここでは自前のエラー型でそれを示してみます。

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())
  • 他のエラーと問題なく連携できる
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

エラーをBoxする

元のエラーを維持しながらシンプルなコードを書くには、Boxしてしまうと良いでしょう。欠点として、元のエラー型はランタイムまで判明せず、静的に決定されないことが挙げられます。

標準ライブラリはBoxに、Fromを介してあらゆるErrorトレイトを実装した型からBox<Error>トレイトオブジェクトへの変換を実装させることで、エラーをboxしやすくしてくれます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

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は消えてなくなります。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

これでかなり綺麗になりました。元のpanicと比べ、リターン型がResultであることを除けば、unwrapの呼び出しを?で置き換えたものに非常に似ています。結果、そのResultは上のレベルで分解されなければなりません。

参照

From::from and ?

エラーをラップする

Boxする方法の代替として、エラーを自前のエラー型としてラップする方法もあります。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

これはエラーの処理のボイラープレートを増やしてしまい、全てのアプリケーションで必要になる訳では無いでしょう。これらのボイラープレートの処理を代わりにやってくれるようなライブラリもあります。

参照

From::from and Enums

Resultをイテレートする

Iter::mapオペレーションは失敗することもあります。例えば、

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

ここでは、この対処法についてみてみましょう。

filter_map()を使って失敗した要素のみを無視する

filter_mapは関数を呼び出し、結果がNoneになるものだけ取り除きます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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.

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

collect()で処理全体を失敗させる

Resultは、それらのベクトル(Vec<Result<T, E>>)からベクトルのそれ(Result<Vec<T>, E>)へと変換できるようにするため、FromIteratorを実装します。Result::Errが見つかり次第、イテレーションは終了します。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

同じテクニックは、Optionを用いて行うこともできます。

partition()を使って全ての正常な値と失敗をまとめる

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

結果を見てみると、まだ全てResultにラップされていることに気づくでしょう。もう少しのボイラープレートが必要です。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

標準ライブラリの型

stdライブラリは、基本データ型を劇的に拡張するカスタム型を数多く提供します。例えば以下です。

  • 拡張可能な文字列であるString。例えば: "hello world"
  • オプション型: Option<i32>
  • エラーハンドリング用のResult<i32, i32>
  • ヒープ上資源のポインタBox<i32>

参照

基本データ型, stdライブラリ

Box, スタックとヒープ

Rustにおいて、すべての値はデフォルトでスタックに割り当てられます。Box<T>を作成することで、値を ボックス化 、すなわちヒープ上に割り当てることができます。ボックスとは正確にはヒープ上におかれたTの値へのスマートポインタです。ボックスがスコープを抜けると、デストラクタが呼ばれて内包するオブジェクトが破棄され、ヒープメモリが解放されます。

ボックス化された値は*オペレータを用いてデリファレンスすることができます。これにより一段と直接的な操作が可能になります。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

ベクタ型

「ベクタ」はサイズを変更可能な配列です。スライスと同様、そのサイズはコンパイル時には不定ですが、いつでも要素を追加したり削除したりすることができます。ベクタは3つの要素で、その特徴が完全に決まります。

  • データへのポインタ
  • 長さ
  • 容量 ... あらかじめメモリ上にベクタのために確保された領域

ベクタはその容量を超えない限りにおいて長くしていくことができます。超えた場合には、より大きな容量を持つように割り当てなおされます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Vec型のメソッドの一覧はstd::vecモジュールを見てください。

文字列

Rustには文字列を扱う型が2つあります。String&strです。

Stringは有効なUTF-8の配列であることを保証されたバイトのベクタ(Vec<u8>)として保持されます。ヒープ上に保持され、伸長可能で、末端にnull文字を含みません。

&strは有効なUTF-8の配列のスライス(&[u8])で、いつでもStringに変換することができます。&[T]がいつでもVec<T>に変換できるのと同様です。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

str/Stringのメソッドをもっと見たい場合はstd::strstd::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: "\"", '\''.

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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.

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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!

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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)、型Tvalueをラップするタプルです。
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Result

これまでの例で、失敗する可能性のある関数の返り値として、列挙型Optionが使用でき、失敗時の返り値にはNoneを用いることを見てきました。しかし、時には なぜ そのオペレーションが失敗したのかを明示することが重要な場合があります。そのためにはResult列挙型を使用します。

列挙型Result<T, E>は2つの値をとりえます。

  • Ok(value) ... これはオペレーションが成功したことを意味し、返り値valueをラップします。(valueは型Tを持ちます。)
  • Err(why) ... これはオペレーションの失敗を意味します。whyをラップしており、ここには失敗した理由が(必ずではありませんが)書かれています。(whyの型はEです。)
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

?

マッチを利用して結果をチェインするのは中々面倒です。 幸いなことに、?マクロを使用すればイケてるコードに戻すことができます。 ?Resultを返す式の末尾で使います。 Err(err)の分岐がreturn Err(From::from(err))という早期リターンに展開され、 Ok(ok)の分岐がokの式に展開されるようなマッチ式と等価です。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

公式ドキュメントをチェックすることをオススメします。 Result型を扱う関数やResult型のメソッドが多く挙げられています。

panic!

panic!マクロはパニックを生成し、スタックの巻き戻しを開始するために使用することができます。巻き戻しの間、ランタイムは、(訳注: panicを起こした)スレッドが 所有権を持つ 全ての資源のデストラクタを呼び出し、メモリ上から解放します。

今回はシングルスレッドのプログラムを実行しているので、panic!はプログラムにパニックメッセージを表示させ、exitします。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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のキーはブーリアン、整数、文字列等のEqHashトレイトを保持する型なら何でもOKです。次のセクションでより詳しく見ていきます。

ベクタ型と同様、伸長可能ですが、HashMapの場合さらに、スペースが余っているときには小さくすることも可能です。HashMapを一定の容量のエリアに作成するときはHashMap::with_capacity(uint)を、デフォルトの容量で作成するときはHashMap::new()を用います。後者が推奨されています。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

ハッシングやハッシュマップ(ハッシュテーブルと呼ばれることもあります)の仕組みについて、より詳しく知りたい場合はWikipediaのハッシュテーブルのページを見てください。

key型の変種

EqHashトレイトを実装している型ならば、なんでもHashMapのキーになることができます。例えば以下です。

  • bool (キーになりうる値が2つしかないので実用的ではないですが…)
  • intuint、あるいは他の整数型
  • String&str(Tips: Stringをキーにしたハッシュマップを作製した場合、.get()メソッドの引数に&strを与えて値を取得することができます。)

f32f64Hashを実装して いない ことに注意しましょう。おそらくこれは浮動小数点演算時に誤差が発生するため、キーとして使用すると、恐ろしいほどエラーの元となるためです。

集合型は、その要素となっている全ての型がEqを、あるいはHashを実装している場合、必ず同じトレイトを実装しています。例えば、Vec<T>THashを実装している場合、Hashを実装します。

独自の型にEqあるいはHashを実装するのは簡単です。以下の一行で済みます。 #[derive(PartialEq, Eq, Hash)]

後はコンパイラがよしなにしてくれます。これらのトレイトの詳細をコントロールしたい場合、EqHashを自分で実装することもできます。この文書ではHashトレイトを実装する方法の詳細については触れません。

structHashMapで扱う際の例として、とてもシンプルなユーザーログインシステムを作成してみましょう。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

ハッシュ集合

値がなく、キーだけのHashMapを想像してみてください。これはハッシュ集合(HashSet)と呼ばれるものです。(HashSet<T>は、実際にはHashMap<T, ()>のラッパーです。)

「何の意味があるの?フツーにキーをVecに入れればいいじゃん」そう思いましたね?

それは、HashSet独自の機能として、要素に重複がないということが保証されるためです。これは全ての集合(set)型がもつ機能です。HashSetはその実装の1つであり、他にはBTreeSet等があります。

HashSetに、すでに存在する値を加えようとすると、(すなわち、加えようとしている値のハッシュ値と、要素中のいずれかの値のハッシュ値が等しい場合、)新しい値によって古い値が上書きされます。

これは、同じ値を2つ以上欲しくない場合や、すでにある値を持っているか知りたい場合にとても有効です。

しかし、集合型の機能はそれだけではありません。

集合型には4つの主要なメソッドがあり、(すべてイテレータを返します。)

  • union: 2つの集合型のどちらか一方にある値を全て取得
  • difference: 1つ目の集合にあり、かつ2つ目には存在しない値を全て取得。
  • intersection: 両方の集合にある値のみを取得。
  • symmetric_difference: どちらか一方の集合には存在するが、両方には ない 値を取得

以下の例でこれらをすべて見ていきましょう。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

例は公式ドキュメントから持ってきています。

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.

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

参照

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.

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

標準ライブラリのその他

他にも、様々な型がstdライブラリの中で提供されています。例えば以下の機能を果たすための物があります。

  • スレッド
  • チャネル
  • ファイルI/O

これらにより基本データ型の提供する機能よりも遥かに豊かなことが実現できます。

参照

基本データ型, stdライブラリ

スレッド

Rustはspawn関数を用いてOSのネイティブスレッドを開始することができます。この関数の引数はmoveクロージャ(訳注: 参照ではなく値を取るクロージャ。 詳しくはクロージャを返す関数を参照)です。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

これらのスレッドのスケジューリングは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 Mutexes or Channels.)

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.

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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.

参照

チャネル

Rustは、スレッド間のコミュニケーションのために、非同期のチャネル(channels)を提供しています。チャネル2つのエンドポイント、すなわち送信者(Sender)と受信者(Receiver)を介して、情報の一方向への流れを作り出すことを可能にしています。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Path

構造体Pathは、ファイルシステム中のパスを表します。Pathには2つの変種があります。UNIXライクなファイルシステムのためのposix::Pathと、Windows用のwindows::Pathです。それぞれプラットフォームに対応したPathをエクスポートします。

PathOsStrから作ることができます。そうすればそのパスが指すファイル・ディレクトリの情報を取得するためのメソッドがいくつか使えるようになります。

Pathはイミュータブルです。Pathの所有権ありのバージョンがPathBufです。 PathPathBufの関係は、strStringの関係に似ています。 PathBufはそのまま変更でき、Pathにデリファレンスすることができます。

Pathの実態はUTF-8の文字列 ではなくOsStringであることに注意しましょう。したがって、Path&strに変換するのは無条件 ではなく 、失敗する可能性があります。それゆえOption型が返されます。 しかしPathからOsStringあるいは&OsStrへの変換はそれぞれinto_os_stringas_os_strによって無条件でできます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

他のPathメソッド(posix::Pathwindows::Path)をチェックするのを忘れずに!それとMetadata構造体も見ておくことをオススメします。

参照

OsStr ならびに Metadata

ファイル I/O

File構造体は開かれたファイルを表し(実際にはファイルディスクリプタのラッパーです)、読み込み・書き込み権限のどちらか一方、あるいは両方を提供します。

これはI/Oに関するオペレーションの失敗をより明瞭にします。このおかげでプログラマは直面した失敗を全て見ることができ、より生産的な方法でそれらを扱うことが可能になります。

open

open関数を用いることで読み込み専用モードでファイルを開くことが可能です。

Fileはファイルディスクリプタという資源を保持しており、drop時にはファイルを閉じるところまで面倒を見てくれます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

以下が成功時に期待されるアウトプットです。

$ 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

単純なやり方

テキストファイルの行を読み込むのを、初心者が初めて実装した場合、 以下のようになるでしょう。

#![allow(unused)] fn main() { use std::fs::read_to_string; fn read_lines(filename: &str) -> Vec<String> { let mut result = Vec::new(); for line in read_to_string(filename).unwrap().lines() { result.push(line.to_string()) } result } }

lines()メソッドはファイルの各行のイテレータを返すので、 インラインでマップを実行し結果を収集することもできます。 そうすると、より簡潔で読みやすい表現となります。

#![allow(unused)] fn main() { use std::fs::read_to_string; fn read_lines(filename: &str) -> Vec<String> { read_to_string(filename) .unwrap() // panic on possible file-reading errors // ファイル読み込みエラーの場合はパニックする。 .lines() // split the string into an iterator of string slices // 文字列のスライスのイテレータに分割する。 .map(String::from) // make each slice into a string // スライスを文字列に変換する。 .collect() // gather them together into a vector // ベクタにまとめる。 } }

上の例では、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構造体はプロセスの作成を行います。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

(余裕があれば、上の例でrustcに不正なフラグを渡し、どうなるか見てみましょう)

パイプ

std::Child構造体は実行中の子プロセスを表します。stdinstdoutstderrを介して表面化のプロセスとのやり取りを仲介します。

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) }

参照

cfg!

引数処理

Standard Library

コマンドライン引数はstd::env::argsを介して取得できます。これはそれぞれの引数を文字列としてyieldするイテレータを返します。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
$ ./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を用いて簡単な引数をパースできます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
$ ./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ではテストのために追加の依存パッケージを指定することもできます。

参照

ユニットテスト

テストは、テスト以外のコードが想定通りに動いているかを確かめる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<()>を返し、内部で?を使えるようになりました!これにより、ユニットテストをさらに簡潔に記述できます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

詳細はエディションガイドを参照してください。

パニックをテストする

ある条件下でパニックすべき関数をテストするには、#[should_panic]アトリビュートを使います。このアトリビュートはパニックメッセージをオプションの引数expected =で受け取れます。パニックの原因が複数あるときに、想定した原因でパニックが発生したことを確認できます。

pub fn divide_non_zero_result(a: u32, b: u32) -> u32 { if b == 0 { panic!("Divide-by-zero error"); } else if a < b { panic!("Divide result is zero"); } a / b } #[cfg(test)] mod tests { use super::*; #[test] fn test_divide() { assert_eq!(divide_non_zero_result(10, 2), 5); } #[test] #[should_panic] fn test_any_panic() { divide_non_zero_result(1, 0); } #[test] #[should_panic(expected = "Divide result is zero")] fn test_specific_panic() { divide_non_zero_result(1, 10); } }

テストを実行すると、次の結果を得られます。

$ cargo test running 3 tests test tests::test_any_panic ... ok test tests::test_divide ... ok test tests::test_specific_panic ... ok test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out Doc-tests tmp-test-should-panic running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

実行するテストを指定する

cargo testにテストの名前を与えると、そのテストだけが実行されます。

$ cargo test test_any_panic running 1 test test tests::test_any_panic ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out Doc-tests tmp-test-should-panic running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

テスト名の一部を指定すると、それにマッチするすべてのテストが実行されます。

$ cargo test panic running 2 tests test tests::test_any_panic ... ok test tests::test_specific_panic ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out Doc-tests tmp-test-should-panic running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

テストを除外する

テストを実行から除外するには、#[ignore]アトリビュートを使います。また、cargo test -- --ignoredで、除外したテストのみを実行できます。

#![allow(unused)] fn main() { pub fn add(a: i32, b: i32) -> i32 { a + b } #[cfg(test)] mod tests { use super::*; #[test] fn test_add() { assert_eq!(add(2, 2), 4); } #[test] fn test_add_hundred() { assert_eq!(add(100, 2), 102); assert_eq!(add(2, 100), 102); } #[test] #[ignore] fn ignored_test() { assert_eq!(add(0, 0), 0); } } }
$ cargo test running 3 tests test tests::ignored_test ... ignored test tests::test_add ... ok test tests::test_add_hundred ... ok test result: ok. 2 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out Doc-tests tmp-ignore running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out $ cargo test -- --ignored running 1 test test tests::ignored_test ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out Doc-tests tmp-ignore running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

ドキュメンテーションテスト

Rustのプロジェクトでは、ソースコードに注釈する形でドキュメントを書くのが主流です。ドキュメンテーションコメントの記述は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はよく似た機能を持ちますが、後者は必ず有効なデータを指していることが借用チェッカーによって保証されているので、常に安全です。生ポインタのデリファレンスはアンセーフなブロックでしか実行できません。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

安全でない関数呼び出し

関数は unsafe として宣言できます。これはコンパイラの代わりにプログラマの責任で正しさを保証することを意味します。 例として std::slice::from_raw_parts があります。この関数は最初の要素へのポインタと長さを指定してスライスを作成します。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

slice::from_raw_parts は、以下の仮定に基づいて処理します。

  • 渡されたポインタが有効なメモリ位置を指していること
  • そのメモリに格納された値が正しい型であること

この仮定を満たさない場合、プログラムの動作は不定となり、何が起こるかわかりません。

インラインアセンブリ

Rustはasm!マクロによってインラインアセンブリをサポートしています。 コンパイラが生成するアセンブリに、手書きのアセンブリを埋め込むことができます。 一般的には必要ありませんが、要求されるパフォーマンスやタイミングを達成するために必要な場合があります。 カーネルコードのような、低レベルなハードウェアの基本要素にアクセスする場合にも、この機能が必要でしょう。

注意: 以下の例はx86/x86-64アセンブリで書かれていますが、他のアーキテクチャもサポートされています。

インラインアセンブリは現在以下のアーキテクチャでサポートされています。

  • x86とx86-64
  • ARM
  • AArch64
  • RISC-V

基本的な使い方

最も単純な例から始めましょう。

#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; unsafe { asm!("nop"); } } }

これは、コンパイラが生成したアセンブリに、NOP (no operation) 命令を挿入します。 すべてのasm!呼び出しは、unsafeブロックの中になければいけません。 インラインアセンブリは任意の命令を挿入でき、不変条件を壊してしまうからです。 挿入される命令は、文字列リテラルとしてasm!マクロの第一引数に列挙されます。

入力と出力

何もしない命令を挿入しても面白くありません。 実際にデータを操作してみましょう。

#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; let x: u64; unsafe { asm!("mov {}, 5", out(reg) x); } assert_eq!(x, 5); } }

これはu64型の変数x5の値を書き込んでいます。 命令を指定するために利用している文字列リテラルが、実はテンプレート文字列になっています。 これはRustのフォーマット文字列と同じルールに従います。 ですが、テンプレートに挿入される引数は、みなさんがよく知っているものとは少し違っています。 まず、変数がインラインアセンブリの入力なのか出力なのかを指定する必要があります。 上記の例では出力となっています。 outと書くことで出力であると宣言しています。 また、アセンブリが変数をどの種類のレジスタに格納するかについても指定する必要があります。 上の例では、regを指定して任意の汎用レジスタに格納しています。 コンパイラはテンプレートに挿入する適切なレジスタを選び、インラインアセンブリの実行終了後、そのレジスタから変数を読みこみます。

入力を利用する別の例を見てみましょう。

#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; let i: u64 = 3; let o: u64; unsafe { asm!( "mov {0}, {1}", "add {0}, 5", out(reg) o, in(reg) i, ); } assert_eq!(o, 8); } }

この例では、変数iの入力に5を加え、その結果を変数oに書き込んでいます。 このアセンブリ特有のやり方として、はじめにiの値を出力にコピーし、それから5を加えています。

この例はいくつかのことを示します。

まず、asm!では複数のテンプレート文字列を引数として利用できます。 それぞれの文字列は、改行を挟んで結合されたのと同じように、独立したアセンブリコードとして扱われます。 このおかげで、アセンブリコードを容易にフォーマットできます。

つぎに、入力はoutではなくinと書くことで宣言されています。

そして、他のフォーマット文字列と同じように引数を番号や名前で指定できます。 インラインアセンブリのテンプレートでは、引数が2回以上利用されることが多いため、これは特に便利です。 より複雑なインラインアセンブリを書く場合、この機能を使うのが推奨されます。 可読性が向上し、引数の順序を変えることなく命令を並べ替えることができるからです。

上記の例をさらに改善して、mov命令をやめることもできます。

#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; let mut x: u64 = 3; unsafe { asm!("add {0}, 5", inout(reg) x); } assert_eq!(x, 8); } }

inoutで入力でもあり出力でもある引数を指定しています。 こうすることで、入力と出力を個別に指定する場合と違って、入出力が同じレジスタに割り当てられることが保証されます。

inoutのオペランドとして、入力と出力それぞれに異なる変数を指定することも可能です。

#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; let x: u64 = 3; let y: u64; unsafe { asm!("add {0}, 5", inout(reg) x => y); } assert_eq!(y, 8); } }

遅延出力オペランド

Rustコンパイラはオペランドの割り当てに保守的です。 outはいつでも書き込めるので、他の引数とは場所を共有できません。 しかし、最適なパフォーマンスを保証するためには、できるだけ少ないレジスタを使うことが重要です。 そうすることで、インラインアセンブリブロックの前後でレジスタを保存したり再読み込みしたりする必要がありません。 これを達成するために、Rustはlateout指定子を提供します。 全ての入力が消費された後でのみ書き込まれる出力に利用できます。 この指定子にはinlateoutという変化形もあります。

以下は、releaseモードやその他の最適化された場合に、inlateoutを利用 できない 例です。

#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; let mut a: u64 = 4; let b: u64 = 4; let c: u64 = 4; unsafe { asm!( "add {0}, {1}", "add {0}, {2}", inout(reg) a, in(reg) b, in(reg) c, ); } assert_eq!(a, 12); } }

上記はDebugモードなど最適化されていない場合にはうまく動作します。 しかし、releaseモードなど最適化されたパフォーマンスが必要な場合、動作しない可能性があります。

というのも、最適化されている場合、コンパイラはbcが同じ値だと知っているので、 bcの入力に同じレジスタを割り当てる場合があります。 しかし、aについてはinlateoutではなくinoutを使っているので、独立したレジスタを割り当てる必要があります。 もしinlateoutが使われていたら、acに同じレジスタが割り当てられたかもしれません。 そうすると、最初の命令によってcの値が上書きされ、アセンブリコードが間違った結果を引き起こします。

しかし、次の例では、全ての入力レジスタが読み込まれた後でのみ出力が変更されるので、inlateoutを利用できます。

#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; let mut a: u64 = 4; let b: u64 = 4; unsafe { asm!("add {0}, {1}", inlateout(reg) a, in(reg) b); } assert_eq!(a, 8); } }

このアセンブリコードは、abが同じレジスタに割り当てられても、正しく動作します。

明示的なレジスタオペランド

いくつかの命令では、オペランドが特定のレジスタにある必要があります。 したがって、Rustのインラインアセンブリでは、より具体的な制約指定子を提供しています。 regは一般的にどのアーキテクチャでも利用可能ですが、明示的レジスタはアーキテクチャに強く依存しています。 たとえば、x86の汎用レジスタであるeaxebxecxedxebpesiediなどは、その名前で指定できます。

#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; let cmd = 0xd1; unsafe { asm!("out 0x64, eax", in("eax") cmd); } } }

この例では、out命令を呼び出して、cmd変数の中身を0x64ポートに出力しています。 out命令はeaxとそのサブレジスタのみをオペランドとして受け取るため、 eaxの制約指定子を使わなければなりません。

注意: 他のオペランドタイプと異なり、明示的なレジスタオペランドはテンプレート文字列中で利用できません。 {}を使えないので、レジスタの名前を直接書く必要があります。 また、オペランドのリストの中で他のオペランドタイプの一番最後に置かれなくてはなりません。

x86のmul命令を使った次の例を考えてみましょう。

#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; fn mul(a: u64, b: u64) -> u128 { let lo: u64; let hi: u64; unsafe { asm!( // The x86 mul instruction takes rax as an implicit input and writes // the 128-bit result of the multiplication to rax:rdx. // x86のmul命令はraxを暗黙的な入力に取り、 // 128ビットの乗算結果をrax:rdxに書き込む。 "mul {}", in(reg) a, inlateout("rax") b => lo, lateout("rdx") hi ); } ((hi as u128) << 64) + lo as u128 } } }

mul命令を使って2つの64ビットの入力を128ビットの結果に出力しています。 唯一の明示的なオペランドはレジスタで、変数aから入力します。 2つ目のオペランドは暗黙的であり、raxレジスタである必要があります。変数bからraxレジスタに入力します。 計算結果の下位64ビットはraxレジスタに保存され、そこから変数loに出力されます。 上位64ビットはrdxレジスタに保存され、そこから変数hiに出力されます。

クロバーレジスタ

多くの場合、インラインアセンブリは出力として必要のない状態を変更することがあります。 これは普通、アセンブリでスクラッチレジスタを利用する必要があったり、 私たちがこれ以上必要としていない状態を命令が変更したりするためです。 この状態を一般的に"クロバー"(訳注:上書き)と呼びます。 私たちはコンパイラにこのことを伝える必要があります。 なぜならコンパイラは、インラインアセンブリブロックの前後で、 この状態を保存して復元しなくてはならない可能性があるからです。

use std::arch::asm; #[cfg(target_arch = "x86_64")] fn main() { // three entries of four bytes each // 4バイトのエントリー3つ let mut name_buf = [0_u8; 12]; // String is stored as ascii in ebx, edx, ecx in order // Because ebx is reserved, the asm needs to preserve the value of it. // So we push and pop it around the main asm. // (in 64 bit mode for 64 bit processors, 32 bit processors would use ebx) // 文字列はasciiとしてebx, edx, ecxの順に保存されている。 // ebxは予約されているので、アセンブリはebxの値を維持する必要がある。 // 従ってメインのアセンブリの前後でプッシュおよびポップを行う。 // (以下は64ビットプロセッサの64ビットモードの場合。32ビットプロセッサはebxを利用する。) unsafe { asm!( "push rbx", "cpuid", "mov [rdi], ebx", "mov [rdi + 4], edx", "mov [rdi + 8], ecx", "pop rbx", // We use a pointer to an array for storing the values to simplify // the Rust code at the cost of a couple more asm instructions // This is more explicit with how the asm works however, as opposed // to explicit register outputs such as `out("ecx") val` // The *pointer itself* is only an input even though it's written behind // いくつかのアセンブリ命令を追加してRustのコードを単純化するために // 値を格納する配列へのポインタを利用する。 // しかし、`out("ecx") val`のような明示的なレジスタの出力とは違い、 // アセンブリの動作をより明示的にする。 // *ポインタそのもの* は後ろに書かれていても入力にすぎない。 in("rdi") name_buf.as_mut_ptr(), // select cpuid 0, also specify eax as clobbered // cpuid 0を選択し、eaxをクロバーに指定する inout("eax") 0 => _, // cpuid clobbers these registers too // cpuidは以下のレジスタもクロバーする out("ecx") _, out("edx") _, ); } let name = core::str::from_utf8(&name_buf).unwrap(); println!("CPU Manufacturer ID: {}", name); } #[cfg(not(target_arch = "x86_64"))] fn main() {}

上の例では、cpuid命令を使い、CPUベンタIDを読み込んでいます。 この命令はeaxにサポートされている最大のcpuid引数を書き込み、 ebxedxecxの順にCPUベンダIDをASCIIコードとして書き込みます。

eaxは読み込まれることはありません。 しかし、コンパイラがアセンブリ以前にこれらのレジスタにあった値を保存できるように、 レジスタが変更されたことをコンパイラに伝える必要があります。 そのために、変数名の代わりに_を用いて出力を宣言し、出力の値が破棄されるということを示しています。

このコードはebxがLLVMによって予約されたレジスタであるという制約を回避しています。 LLVMは、自身がレジスタを完全にコントロールし、 アセンブリブロックを抜ける前に元の状態を復元しなくてはならないと考えています。 そのため、コンパイラがin(reg)のような汎用レジスタクラスを満たすために使用する場合 を除いて ebxを入力や出力として利用できません。 つまり、予約されたレジスタを利用する場合に、regオペランドは危険なのです。入力と出力が同じレジスタを共有しているので、知らないうちに入力や出力を破壊してしまうかもしれません。

これを回避するために、rdiを用いて出力の配列へのポインタを保管し、pushebxを保存し、アセンブリブロック内でebxから読み込んで配列に書き込み、popebxを元の状態に戻しています。 pushpopは完全な64ビットのrbxレジスタを使って、レジスタ全体を確実に保存しています。 32ビットの場合、pushpopにおいてebxがかわりに利用されるでしょう。

アセンブリコード内部で利用するスクラッチレジスタを獲得するために、 汎用レジスタクラスとともに使用することもできます。

#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; // Multiply x by 6 using shifts and adds // シフト演算と加算を利用してxに6をかける let mut x: u64 = 4; unsafe { asm!( "mov {tmp}, {x}", "shl {tmp}, 1", "shl {x}, 2", "add {x}, {tmp}", x = inout(reg) x, tmp = out(reg) _, ); } assert_eq!(x, 4 * 6); } }

シンボル・オペランドとABIクロバー

デフォルトでは、asm!は、出力として指定されていないレジスタはアセンブリコードによって中身が維持される、と考えます。 asm!に渡されるclobber_abi引数は、与えられた呼び出し規約のABIに従って、 必要なクロバーオペランドを自動的に挿入するようコンパイラに伝えます。 そのABIで完全に保存されていないレジスタは、クロバーとして扱われます。 複数の clobber_abi 引数を指定すると、指定されたすべてのABIのクロバーが挿入されます。

#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; extern "C" fn foo(arg: i32) -> i32 { println!("arg = {}", arg); arg * 2 } fn call_foo(arg: i32) -> i32 { unsafe { let result; asm!( "call {}", // Function pointer to call // 呼び出す関数ポインタ in(reg) foo, // 1st argument in rdi // 最初の引数はrdiにある in("rdi") arg, // Return value in rax // 戻り値はraxにある out("rax") result, // Mark all registers which are not preserved by the "C" calling // convention as clobbered. // "C"の呼び出し規約で保存されていないすべてのレジスタをクロバーに指定 clobber_abi("C"), ); result } } } }

レジスタテンプレート修飾子

テンプレート文字列に挿入されるレジスタの名前のフォーマット方法について、細かい制御が必要な場合があります。 アーキテクチャのアセンブリ言語が、同じレジスタに別名を持っている場合です。 典型的な例としては、レジスタの部分集合に対する"ビュー"があります(例:64ビットレジスタの下位32ビット)。

デフォルトでは、コンパイラは常に完全なレジスタサイズの名前を選択します(例:x86-64ではrax、x86ではeax、など)。

この挙動は、フォーマット文字列と同じように、テンプレート文字列のオペランドに修飾子を利用することで上書きできます。

#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; let mut x: u16 = 0xab; unsafe { asm!("mov {0:h}, {0:l}", inout(reg_abcd) x); } assert_eq!(x, 0xabab); } }

この例では、reg_abcdレジスタクラスを用いて、レジスタアロケータを4つのレガシーなx86レジスタ (ax, bx, cx, dx) に制限しています。このうち最初の2バイトは独立して指定できます。

レジスタアロケータがxaxレジスタに割り当てることにしたと仮定しましょう。 h修飾子はそのレジスタの上位バイトのレジスタ名を出力し、l修飾子は下位バイトのレジスタ名を出力します。 したがって、このアセンブリコードはmov ah, alに展開され、値の下位バイトを上位バイトにコピーします。

より小さなデータ型(例:u16)をオペランドに利用し、テンプレート修飾子を使い忘れた場合、 コンパイラは警告を出力し、正しい修飾子を提案してくれます。

メモリアドレスオペランド

アセンブリ命令はオペランドがメモリアドレスやメモリロケーション経由で渡される必要なこともあります。 そのときは手動で、ターゲットのアーキテクチャによって指定されたメモリアドレスのシンタックスを利用しなくてはなりません。 例えば、Intelのアセンブリシンタックスを使うx86/x86_64の場合、入出力を[]で囲んで、メモリオペランドであることを示さなくてはなりません。

#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; fn load_fpu_control_word(control: u16) { unsafe { asm!("fldcw [{}]", in(reg) &control, options(nostack)); } } } }

ラベル

名前つきラベルの再利用は、ローカルかそうでないかに関わらず、アセンブラやリンカのエラーを引き起こしたり、変な挙動の原因となります。 名前つきラベルの再利用は以下のようなケースがあります。

  • 明示的再利用: 同じラベルを1つのasm!ブロック中で、または複数のブロック中で2回以上利用する場合です。
  • インライン化による暗黙の再利用: コンパイラはasm!ブロックの複数のコピーをインスタンス化する場合があります。例えば、asm!ブロックを含む関数が複数箇所でインライン化される場合です。
  • LTO(訳注: Link Time Optimizationの略)による暗黙の再利用: LTOは 他のクレート のコードを同じコード生成単位に配置するため、同じ名前のラベルを持ち込む場合があります。

そのため、インラインアセンブリコードの中では、GNUアセンブラの 数値型 ローカルラベルのみ使用してください。 アセンブリコード内でシンボルを定義すると、シンボル定義の重複により、アセンブラやリンカのエラーが発生する可能性があります。

さらに、x86でデフォルトのIntel構文を使用する場合、LLVMのバグによって、 011101010といった01だけで構成されたラベルは、 バイナリ値として解釈されてしまうため、使用してはいけません。 options(att_syntax)を使うと曖昧さを避けられますが、asm!ブロック 全体 の構文に影響します。 (optionsについては、後述のオプションを参照してください。)

#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; let mut a = 0; unsafe { asm!( "mov {0}, 10", "2:", "sub {0}, 1", "cmp {0}, 3", "jle 2f", "jmp 2b", "2:", "add {0}, 2", out(reg) a ); } assert_eq!(a, 5); } }

このコードは、{0}のレジスタの値を10から3にデクリメントし、2を加え、aにその値を保存します。

この例は、以下のことを示しています。

  • まず、ラベルとして同じ数字を複数回、同じインラインブロックで利用できます。
  • つぎに、数字のラベルが参照として(例えば、命令のオペランドに)利用された場合、"b"("後方")や"f"("前方")の接尾辞が数字のラベルに追加されなくてはなりません。そうすることで、この数字の指定された方向の最も近いラベルを参照できます。

オプション

デフォルトでは、インラインアセンブリブロックは、カスタム呼び出し規約をもつ外部のFFI関数呼び出しと同じように扱われます: メモリを読み込んだり書き込んだり、観測可能な副作用を持っていたりするかもしれません。 しかし、多くの場合、アセンブリコードが実際に何をするかという情報を多く与えて、より最適化できる方が望ましいでしょう。

先ほどのadd命令の例を見てみましょう。

#![allow(unused)] fn main() { #[cfg(target_arch = "x86_64")] { use std::arch::asm; let mut a: u64 = 4; let b: u64 = 4; unsafe { asm!( "add {0}, {1}", inlateout(reg) a, in(reg) b, options(pure, nomem, nostack), ); } assert_eq!(a, 8); } }

オプションはasm!マクロの最後の任意引数として渡されます。 ここでは3つのオプションを利用しました:

  • pureは、アセンブリコードが観測可能な副作用を持っておらず、出力は入力のみに依存することを意味します。これにより、コンパイラオプティマイザはインラインアセンブリの呼び出し回数を減らしたり、インラインアセンブリを完全に削除したりできます。
  • nomemは、アセンブリコードがメモリの読み書きをしないことを意味します。デフォルトでは、インラインアセンブリはアクセス可能なメモリアドレス(例えばオペランドとして渡されたポインタや、グローバルなど)の読み書きを行うとコンパイラは仮定しています。
  • nostackは、アセンブリコードがスタックにデータをプッシュしないことを意味します。これにより、コンパイラはx86-64のスタックレッドゾーンなどの最適化を利用し、スタックポインタの調整を避けることができます。

これにより、コンパイラは、出力が全く必要とされていない純粋なasm!ブロックを削除するなどして、asm!を使ったコードをより最適化できます。

利用可能なオプションとその効果の一覧はリファレンスを参照してください。

互換性

Rust言語は急速に進化しており、可能な限り前方互換性を確保する努力にも関わらず、特定の互換性の問題が生じることがあります。

生識別子

Rustは多くのプログラミング言語と同様に、「キーワード」の概念を持っています。 これらの識別子は言語にとって何かしらの意味を持ちますので、変数名や関数名、その他の場所で使用することはできません。 生識別子は、通常は許されない状況でキーワードを使用することを可能にします。 これは、新しいキーワードを導入したRustと、古いエディションのRustを使用したライブラリが同じ名前の変数や関数を持つ場合に特に便利です。

例えば、2015年エディションのRustでコンパイルされたクレートfootryという名前の関数をエクスポートしているとします。 このキーワードは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(); }

周辺情報

この章では、プログラミングそれ自体に関係はないけれども、色々と人々の役に立つ機能やインフラについて説明していきます。例えば:

ドキュメンテーション

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を使用することができます。ドキュメンテーションコメントは大規模なプロジェクトの際に非常に有用です。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

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:

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

For documentation, rustdoc is widely used by the community. It's what is used to generate the std library docs.

参照

プレイグラウンド

Rust Playgroundでは、RustのコードをWebのインターフェースを通じて実験できます。

mdbookと組み合わせる

mdbookでは、コード例を実行・編集可能にできます。

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

これにより、読者はあなたのコード例を実行するだけでなく、変更することもできます。 editableという単語をカンマで区切って、あなたのコードブロックに追加するのがキーです。

```rust,editable //...ここにあなたのコードを書きます ```

加えて、mdbookがビルドやテストを実行するときに、あなたのコードをスキップさせたい場合は、ignoreを追加できます。

```rust,editable,ignore //...ここにあなたのコードを書きます ```

ドキュメントと組み合わせる

Rustの公式ドキュメントには、「実行(Run)」ボタンがある場合があります。 クリックすると、新しいタブでRust Playgroundが開き、コード例が表示されます。 この機能は、#[doc]アトリビュートのhtml_playground_urlの値で有効化できます。

参照