The Rust Guide (Japanese Translation):後篇 §15-§27

  • 37
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

この文書はRust Guide (0.12.0 nightly) http://doc.rust-lang.org/guide.html の日本語訳です。
このページは後半§15-§27の翻訳です。前半§01-§14はhttp://qiita.com/lex_naturalis/items/2ae75a2528c52ac3e79e です。

15. クレイトとモジュール

Rustは強力なモジュール・システムを特徴としていますが、その動作の仕方は他のプログラミング言語とは少し違っています。Rustのモジュール・システムには、 クレイトモジュール という2つの構成要素があります。

クレイトは、独立したコンパイルの単位です。Rustは常に一度にひとつのクレイトをコンパイルし、ライブラリか実行可能ファイルを生成します。しかし、実行可能ファイルは通常、ライブラリに依存し、多くのライブラリは他のライブラリに依存しています。これをサポートするために、クレイトは他のクレイトに依存することができるようになっています。

各クレイトには、モジュールの階層構造が含まれています。このモジュールの木構造は、 クレイト・ルート と呼ばれる単一のモジュールから始まります。クレイト・ルートの内部では、他のモジュールを宣言することができ、このモジュールも他のモジュールを含むことができます。これは任意の深さまで好きなだけ続けることができます。

ファイルについては何も言及していないことに注意してください。Rustはモジュール構造とファイルシステムの構造との間に何らの特別な関係も要求しません。それはそれとして、ファイルシステム上でRustがモジュールをどのように探索するかについては規約的なやり方があります。しかし、これは書換可能なものです。

おしゃべりは充分です。なにかビルドしてみましょう。modulesという名前の新規プロジェクトを作成してください:

$ cd ~/projects
$ cargo new modules --bin
$ cd modules
Let's double check our work by compiling:

$ cargo run
   Compiling modules v0.0.1 (file:///home/you/projects/modules)
     Running `target/modules`
Hello, world!

エクセレント! というわけで、これでもう既にsrc/main.rsという単一のクレイトがあるわけです。このファイルの中にはあるものは全てクレイト・ルートに入っています。ちょうどいましたように、実行可能ファイルを生成するクレートは、ルートの内部でmain函数を定義することになります。

クレイトの内部で新しいモジュールを定義しましょう。src/main.rsを次のように変更してください:

fn main() {
    println!("Hello, world!");
}

mod hello {
    fn print_hello() {
        println!("Hello, world!");
    }
}

これで、helloという名前のモジュールがクレイト・ルート内にできました。モジュールには函数や変数束縛と同様に、_を使ったスネークケースによる命名をします。

helloモジュール内では、print_hello函数を定義しています。また、この函数ははhello worldというメッセージを表示します。モジュールによって、共通のものをひとまとめにし異なったものは別々にすることで、機能毎の整頓された箱へとプログラムを分割することが可能になります。どんなものにもそれのしかるべき場所が用意されているような、一揃いの戸棚があるようなものです。

print_hello函数を呼び出すには、ダブルコロン(::)を使います:

hello::print_hello();

これは、以前にでてきたio::stdin()rand::random()の時にも目にしましたね。今度は自分でそれを作ることができるようになったわけです。しかしながら、クレイトとモジュールには、あるモジュールで定義された函数を誰が使ってよいかを制御する、 可視性 に関するルールがあります。デフォルトでは、モジュール内部にある全てのものは非公開(private)です。つまり、同じモジュール内の他の函数からしかそれを使うことができません。したがって、以下のコードはコンパイルできません:

fn main() {
    hello::print_hello();
}

mod hello {
    fn print_hello() {
        println!("Hello, world!");
    }
}

これはエラーになります:

   Compiling modules v0.0.1 (file:///home/you/projects/modules)
src/main.rs:2:5: 2:23 error: function `print_hello` is private
src/main.rs:2     hello::print_hello();
                  ^~~~~~~~~~~~~~~~~~

これを公開(public)にするにはpubというキーワードを使います:

fn main() {
    hello::print_hello();
}

mod hello {
    pub fn print_hello() {
        println!("Hello, world!");
    }
}

pubキーワードを使うことを、それによって函数を他のモジュールから使用可能にすることになるので、「エクスポートする」と呼ぶことがあります。

$ cargo run
   Compiling modules v0.0.1 (file:///home/you/projects/modules)
     Running `target/modules`
Hello, world!

ナイスです! モジュールをそれぞれのファイルに移動するとか、モジュールについてできることはもっとありますが、今のところはこれで充分です。

16. テスト

伝統的には、ソフトウェアテストが強みであるというようなシステムプログラミング言語は殆どありませんでした。しかしながら、Rustはとても基本的なテストを言語自体に組み込んでいます。自動化されたテストによってコードにバグがないことを証明することはできませんが、ある特定の挙動が意図通りに動作しているかを検証するには有益です。

これはとても基本的なテストの例です:

#[test]
fn is_one_equal_to_one() {
    assert_eq!(1i, 1i);
}

#[test]という、見慣れないものに気がついたことでしょう。テストの動作機構に踏み込む前に、属性についてお話しましょう。

16.1. 属性

どの函数がテストであるかを標識するために、Rustのテストシステムは 属性 attributesというものを使います。属性は任意のRustの 項目 item に対して設定することができます。Rustでは殆どのものが式ですが、letはそうではない、ということを覚えていますか? 同様に、アイテム宣言もまた式ではありません。以下のリストがアイテムとして扱われるものの一覧です:

  • 函数
  • モジュール
  • 型定義
  • 構造体
  • 列挙型
  • static 項目
  • トレイト
  • 実装宣言

まだこれらの全てについて学んだわけではありませんが、しかしこれが一覧です。見ての通り、函数がトップに来ていますね。

属性の書き方には次の3つの方式があります:

  1. 単一の識別子、つまり属性名。たとえば、 #[test]
  2. 識別子に続けて=記号とリテラル。たとえば、#[cfg=test].
  3. 識別子に続けて、副属性の引数リストを括弧で括ったもの。たとえば、#[cfg(unix, target_word_size = "32")]。この例では副属性のうちひとつが第2の種類のものになっていることに注意してください。

属性には多数の種類がありますが、ここでそれらの全てに渡って話をせずとも充分でしょう。テスト特有の属性について話をするまえに、属性の中でも最も重要な種類のものである安定性マーカーについて触れておきましょう。

16.2. 安定性属性

Rustは、ライブラリの様々な部分についてその安定性のレベルを表示するための6つの属性を提供しています。この6つの安定性レベルは以下のとおりです:

  • deprecated: この属性が附された項目はもはや使われるべきではありません。後方互換性はなんら保証されません。
  • experimental: この属性が附された項目はごく最近に導入されたものか、そうでないとしても流動的状態にあります。大幅な変更が行われるかもしれませんし、取り除かれてしまうこともあるかもしれません。後方互換性はなんら保証されません。
  • unstable: この属性が附された項目はなお開発中であり、安定していると考えられるまでになおいっそうのテストを必要としています。後方互換性はなんら保証されません。
  • stable: この属性が附された項目は安定していると考えられ、大幅な変更はなされません。後方互換性が保証されます。
  • frozen: この属性が附された項目は大変に安定しており、変更がなされることは恐らくありません。後方互換性が保証されます。
  • locked: この属性が附された項目は、深刻なバグが発見されない限りは、決して変更されません。後方互換性が保証されます。

Rust標準ライブラリの全ての項目は、これらの属性マーカーによってその相対的安定性を伝達しています。あなたも自身のコードで同様にこれらを用いるべきです。またこれらに関連する属性warnがありますが、これはあなたが、deprecatedexperimentalunstableといった特定の安定性レベルで標識された項目をインポートする際に、そのことを警告するために使います。今のところはデフォルトでdeprecatedのみ警告が行われますが、いったん標準ライブラリが安定化したならば、この設定は変更されるでしょう。

warn属性は次のように使います:

#![warn(unstable)]

そしてその後で、クレイトをインポートすると:

extern crate some_crate;

もしunstableと標識された何かを使用しているならば、警告が出ます。

warn属性宣言の感嘆符に気がついたことでしょう。この属性の!は、この属性がこれに後続する項目ではなく、これを含む項目に適用されることを示しています。ですから、このwarn属性宣言は、この後にどんな宣言項目が続くにせよ、それらではなくあくまでこれを含んでいるクレイトそれ自体に適用されます。

// これは現在のクレイトに適用されます。
#![warn(unstable)]

extern crate some_crate;

// これは後続の`fn`に適用されます。
#[test]
fn a_test() {
  // ...
}

16.3. テストを書く

テスト駆動開発的なやり方で、ごく簡単なクレイトを書いてみましょう。もう手順はわかってますよね。新規プロジェクトを作りましょう:

$ cd ~/projects
$ cargo new testing --bin
$ cd testing

そうしたら試してみましょう:

$ cargo run
   Compiling testing v0.0.1 (file:///home/you/projects/testing)
     Running `target/testing`
Hello, world!

素晴らしい。Rustのインフラは2種類のテストのための2種類の場所でのテストをサポートしています。まずクレイトそれ自体の中に ユニットテスト を含めることができ、他方でtestsディレクトリの中に 結合テスト を置くことができます。「ユニットテスト」は、あるユニットにフォーカスを当てた小規模のテストであるのに対し、「結合テスト」の方は結合された複数のユニットをテストします。それはそうなのですが、この区分は社会的規約に過ぎず、両者の間で構文上の違いはありません。testsディレクトリを作りましょう:

$ mkdir tests

次にtests/lib.rsに結合テストを作ります:

#[test]
fn foo() {
    assert!(false);
}

テスト用の函数にはどのようなな名前をつけても構いませんが、内容を記述するような名前にしておくとよいでしょう。その理由は少ししたらわかります。それから、assert!マクロを使って、何かが真であるということを表明します。今回の場合、falseを与えていますから、このテストは失敗するはずです。やってみましょう!


$ cargo test
   Compiling testing v0.0.1 (file:///home/you/projects/testing)
/home/you/projects/testing/src/main.rs:1:1:
  3:2 warning: code is never used: `main`, #[warn(dead_code)] on by default
/home/you/projects/testing/src/main.rs:1 fn main() {
/home/you/projects/testing/src/main.rs:2     println!("Hello, world");
/home/you/projects/testing/src/main.rs:3 }

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured


running 1 test
test foo ... FAILED

failures:

---- foo stdout ----
        task 'foo' failed at 'assertion failed: false',
/home/you/projects/testing/tests/lib.rs:3



failures:
    foo

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured

task '<main>' failed at 'Some tests failed', /home/you/src/rust/src/libtest/lib.rs:242

ずいぶんたくさん出力されました! 分解して見て行きましょう:

$ cargo test
   Compiling testing v0.0.1 (file:///home/you/projects/testing)

全てのテストはcargo testで実行することができます。testsディレクトリの中のテストと、クレイトの中に置いたテストの両方が、実行されます。

/home/you/projects/testing/src/main.rs:1:1:
 3:2 warning: code is never used: `main`, #[warn(dead_code)] on by default
/home/you/projects/testing/src/main.rs:1 fn main() {
/home/you/projects/testing/src/main.rs:2     println!("Hello, world");
/home/you/projects/testing/src/main.rs:3 }

Rustにはデフォルトで「到達不能コード警告」と呼ばれる lintプログラム があります。lintは、あなたのコードをチェックして、それについて色々と教えてくれるプログラムです。今回の場合Rustは、決して使われないコードを書いてしまっていますよ、と警告しています。つまり、main函数です。
もちろんテストを実行しているところなのですから、main函数は使いません。もう少ししたら、この函数についてのlintをオフにしますが、今のところはこの出力は無視しておいてください。

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured

ちょっと待った、テストがゼロ? でもテストを定義したんじゃなかったっけ? うん。実はこの出力は、クレイト内のテストを実行しようとした結果なのですが、確かにクレイト内ではなんのテストも定義していませんでしたよね。Rustがそれぞれの種類のテストについて成功、失敗、無視、測定済、という結果を報告していることに気が付きましたか。「測定済み」のテストというのは、ベンチマークテストのことを指しています。これについてもすぐにお話します。

running 1 test
test foo ... FAILED

ようやく、本題にやってきました。テストにきちんとした名前をつけるように、とお話したことを覚えていますか? Rustは私達が付けた名前「foo」にしたがって「test foo」と言っています。もしきちんとした名前をつけていれば、どのテストが失敗したかがもっとはっきりわかります。とりわけ、テストがもっと貯まってきたときには。

失敗は:

---- foo stdout ----
        task 'foo' failed at 'assertion failed: false', /home/you/projects/testing/tests/lib.rs:3



failures:
    foo

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured

task '<main>' failed at 'Some tests failed', /home/you/src/rust/src/libtest/lib.rs:242

全部のテストが実行された後で、Rustは失敗したテストからの出力を示します。いまの場合、Rustはfalse値によって表明が失敗している旨を告知します。これは期待したとおりの動作でしたね。

ヒュ~! さて、テストを直しましょう:

#[test]
fn foo() {
    assert!(true);
}

で、もう一度テストを実行してみます:

$ cargo test
   Compiling testing v0.0.1 (file:///home/you/projects/testing)
/home/you/projects/testing/src/main.rs:1:1: 3:2 warning: code is never used: `main`, #[warn(dead_code)] on by default
/home/you/projects/testing/src/main.rs:1 fn main() {
/home/you/projects/testing/src/main.rs:2     println!("Hello, world");
/home/you/projects/testing/src/main.rs:3 }

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured


running 1 test
test foo ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

よし! 期待通りテストがパスしました。main函数に対する警告を除去しましょう。src/main.rsを次のように変更してください:

#[cfg(not(test))]
fn main() {
    println!("Hello, world");
}

この属性はcfgnotという2つのものを組み合わせたものです。cfg属性によって、何かに基づいてコードを条件的にコンパイルすることができます。この属性に続く項目は、もし設定によってそれを真とされている時に限って、コンパイルされます。テストをコンパイルするときに、Cargoはcfg(test)が真になるように設定します。しかし、ここで我々はmain函数を、それが真でない時にこそ、コードに含めたいわけです。ですから、否定をするためにnotを使います。ということはつまり、cfg(not(test))は、cfg(test)が偽であるときに限って、コードをコンパイルすることになります。

この属性を使えば、警告がなくなります:

$ cargo test
   Compiling testing v0.0.1 (file:///home/you/projects/testing)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured


running 1 test
test foo ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

よしよし。さて、今度は本物のテストを書きましょう。tests/lib.rsを次のように変更してください:

#[test]
fn math_checks_out() {
    let result = add_three_times_four(5i);

    assert_eq!(32i, result);
}

And try to run the test:

そしてテストを実行してみましょう:

$ cargo test
   Compiling testing v0.0.1 (file:///home/youg/projects/testing)
/home/youg/projects/testing/tests/lib.rs:3:18: 3:38 error: unresolved name `add_three_times_four`.
/home/youg/projects/testing/tests/lib.rs:3     let result = add_three_times_four(5i);
                                                            ^~~~~~~~~~~~~~~~~~~~
error: aborting due to previous error
Build failed, waiting for other jobs to finish...
Could not compile `testing`.

To learn more, run the command again with --verbose.

Rustにはこのadd_three_times_four函数が見つけられていません。そりゃそうですよね、だってまだそれを書いていないのですから!

このコードをテストと共有するためには、ライブラリ・クレイトを作成しなければなりません。それにまた、これはよいソフトウェア設計でもあります。前に述べたように、自作の機能の殆どはライブラリ・クレイトに突っ込んでおいて、実行可能ファイルにはそのライブラリを使わせる、というのがよい考えです。そうするとコードの再利用ができますから。

そのためには新規のモジュールを作成する必要があります。src/lib.rsという新しいファイルを作って、そこに以下のように書きましょう:

pub fn add_three_times_four(x: int) -> int {
    (x + 3) * 4
}

このファイルはlib.rsという名前にしました。これはプロジェクトと同じ名前なので、命名規約としてこういう名前にします。

それから、このクレイトをsrc/main.rsから利用する必要があります:

extern crate testing;

#[cfg(not(test))]
fn main() {
    println!("Hello, world");
}

最後に、この函数をtests/lib.rsでインポートしましょう:

extern crate testing;
use testing::add_three_times_four;

#[test]
fn math_checks_out() {
    let result = add_three_times_four(5i);

    assert_eq!(32i, result);
}

実行してみましょう:

$ cargo test
   Compiling testing v0.0.1 (file:///home/you/projects/testing)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured


running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured


running 1 test
test math_checks_out ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

素晴らしい! テストがひとつパスしましたね。publicなメソッドが動作していることを示す結合テストができたわけです。しかし、内部ロジックについてもテストしたいですよね。この函数は単純ですが、もしそれがもっと複雑だったとしたら、更なるテストが必要だったはずだということは容易に想像が付きます。ですから、この函数を2つの補助函数へと分割し、それをテストするユニットテストを書いてみましょう。

src/lib.rsを次のように変更してください:

pub fn add_three_times_four(x: int) -> int {
    times_four(add_three(x))
}

fn add_three(x: int) -> int { x + 3 }

fn times_four(x: int) -> int { x * 4 }

cargo testを実行すると、同じ出力が得られるはずです:

$ cargo test
   Compiling testing v0.0.1 (file:///home/you/projects/testing)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured


running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured


running 1 test
test math_checks_out ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

もしこの2つの新しい函数に対してテストを書こうとしたとしても、うまく動作しなかったはずです。たとえばこうしたとしても:

extern crate testing;
use testing::add_three_times_four;
use testing::add_three;

#[test]
fn math_checks_out() {
    let result = add_three_times_four(5i);

    assert_eq!(32i, result);
}

#[test]
fn test_add_three() {
    let result = add_three(5i);

    assert_eq!(8i, result);
}

こういうエラーになります:

   Compiling testing v0.0.1 (file:///home/you/projects/testing)
/home/you/projects/testing/tests/lib.rs:3:5: 3:24 error: function `add_three` is private
/home/you/projects/testing/tests/lib.rs:3 use testing::add_three;
                                              ^~~~~~~~~~~~~~~~~~~

まさしくその通り。add_three函数はprivateです。なので、外部結合テストはうまくいきません。ユニットテストが必要です。src/lib.rsを開いて次のように書き加えましょう:

pub fn add_three_times_four(x: int) -> int {
    times_four(add_three(x))
}

fn add_three(x: int) -> int { x + 3 }

fn times_four(x: int) -> int { x * 4 }

#[cfg(test)]
mod test {
    use super::add_three;
    use super::times_four;

    #[test]
    fn test_add_three() {
        let result = add_three(5i);

        assert_eq!(8i, result);
    }

    #[test]
    fn test_times_four() {
        let result = times_four(5i);

        assert_eq!(20i, result);
    }
}

試してみましょう:

$ cargo test
   Compiling testing v0.0.1 (file:///home/you/projects/testing)

running 1 test
test test::test_times_four ... ok
test test::test_add_three ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured


running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured


running 1 test
test math_checks_out ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

カッコイイ! これで内部函数に対して2つのテストができました。出力が3セット分あることに気が付きましたか。src/main.rsに対してひとつ、src/lib.rsに対してひとつ、そしてtests/lib.rsに対してひとつ、です。あと、まだお話していない、興味深いものが残っていますよね。ここのところです:

use super::add_three;
use super::times_four;

入れ子になったモジュールを作成したので、superを使って親モジュールから函数をインポートしたのです。下位モジュールからは親モジュールのprivateな函数が見えます。

テストの基本事項はこれでおしまいです。Rustのテストツールは原始的ですが、単純なケースではうまく動作します。こうした仕組の上にもっと複雑なフレームワークをつくり上げる作業にとりかかっているRust信者もいるにはいるのですが、それはまだ始まったばかりです。

17. ポインタ

システム・プログラミングでは、ポインタが信じられないほど重要なトピックになります。Rustにはポインタが豊富に揃っていますが、これらのポインタは他の多数の言語と違った動作をします。ポインタはとても重要なので、ポインタの詳細に踏み込む専用のポインタ・ガイドが用意されています。実際のところ、みなさんが読んでいるこのガイドはRust言語を広範囲に概観するものであって、ある特定のトピックを顕微鏡的に視るようなガイドが他に多数あります。それらのガイドの一覧はRustドキュメントのインデックス・ページにあります。

この節では、一般的概念としてのポインタについてはよくわかっているものとします。もしそうでないなら、ポインタ・ガイドの導入セクションを読んで、それから戻ってきてください。お待ちしてます。

さて、骨子はつかみましたか? 大変結構。ではRustのポインタについてお話しましょう。

17.1. 参照

Rustに於ける最も原始的な形態のポインタは 参照 と呼ばれます。参照はアンパサンド記号(&)を用いて作成されます。以下は単純な参照の例です:

let x = 5i;
let y = &x;

yxへの参照です。逆参照・参照解決(dereference)をするには(つまり参照それ自体ではなく参照されている値を得るには)、アスタリスク記号(*)を使います:

let x = 5i;
let y = &x;

assert_eq!(5i, *y);

ほかのlet束縛と同様に、参照はデフォルトでは変更不能です。

参照を受け取る函数を宣言することができます:

fn add_one(x: &int) -> int { *x + 1 }

fn main() {
    assert_eq!(6, add_one(&5));
}

見ての通り、&を適用することでリテラル値からも参照を作成することができます。もちろん、こういう単純な函数の場合には、xを参照で受け取る理由はたいしてありません。これは単に構文の例として挙げただけです。

参照は変更不能ですから、別名関係(alias)になる(つまり同じ場所を指し示すような)複数の参照を作ることができます:

let x = 5i;
let y = &x;
let z = &x;

変更可能な参照は&の代わりに&mutを使うことで作成できます:

let mut x = 5i;
let y = &mut x;

この際にxも変更可能でなければいけないことに注意してください。もしそうでないと:

let x = 5i;
let y = &mut x;

Rustに文句を言われます:

6:19 error: cannot borrow immutable local variable `x` as mutable
 let y = &mut x;
              ^

変更不能なデータに対する変更可能な参照は欲しくないですよね! このエラーメッセージには、まだお話していない用語「借用 borrow」が出てきていますが、それについてはすぐ後に触れます。

実のところ、この単純な例だけでも、Rustのパワーの多くが説明されています。Rustは、コンパイル時に、我々がルールを破ることを阻止してくれているのです。Rustでの参照はこれらのルールを完全にコンパイル時にチェックしてしまうので、実行時にはこの安全性の分についてのオーバーヘッドがありません。実行時には、これらはC言語やC++と同様に、機械上の生のポインタと同じものになります。Rustは、なにも危険なことをしていないことを事前にダブルチェックしているだけ、なのです。

Rustは、別名関係になるような変更可能参照を複数作成することも阻止してくれます。次のコードは動作しません:

let mut x = 5i;
let y = &mut x;
let z = &mut x;

これは次のようなエラーになります:

error: cannot borrow `x` as mutable more than once at a time
     let z = &mut x;
                  ^
note: previous borrow of `x` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `x` until the borrow ends
     let y = &mut x;
                  ^
note: previous borrow ends here
 fn main() {
     let mut x = 5i;
     let y = &mut x;
     let z = &mut x;
 }
 ^

これはまた大量のエラーメッセージになりましたね。詳細に踏み込んでみましょう。このエラーメッセージには3つの部分があります。エラーと2つの注記です。エラーの方は思った通りのことを言っています。つまり、同じメモリを指す2つのポインタは作れません、というわけです。

2つの注記は、更に追加の文脈情報を提供しています。エラーが複雑な場合、Rustのエラーメッセージにはこの種の追加情報が含まれています。Rustが言っているのはこういうことです。第1の注記は、xzとして 借用 できない理由は既にその前にxyとして借用しているからだ、ということで、第2の注記は、yの借用がどこで終了するかを示しています。

ねえ、まって、借用ってなにさ?

このエラーを本当に理解するには、新しい概念を幾つか学ばなければなりません。それが、 所有権借用生存期間 、の3つです。

17.2. 所有権、借用、生存期間

なんらかの種類のリソースが作成される場合には常に、そのリソースを破壊する責任を担ってくれる何かがなければなりません。これは他のリソースについても当てはまるのですが、目下のところポインタの話をしていますから、メモリ割当という文脈でお話することにしましょう。

ヒープのメモリを割り当てる際には、そのメモリを解放するメカニズムが必要です。多くの言語は、メモリ割当の制御をプログラマにさせておいて、解放を処理するにはガーベジコレクタを使います。これは妥当な、時間を経て実証されてきた方策ではあるのですが、欠点もあります。解放については割当と同じほどプログラマがモノを考える必要がないので、割当がそこかしこで行われることになります(なんたって簡単ですからね)。しかし、何かがいつ解放されるのかに対する精確な制御を行いたいときには、ランタイム任せではそれが難しくなってしまうのです。

Rustは別の道筋を選択しています。それが 所有権 というものです。あるリソースを作成する束縛はなんであれそのリソースの 所有者 になります。

所有者であるということからは幾つかの特権が得られます:

  1. そのリソースがいつ解放されるかを制御できます。
  2. そのリソースを、変更不能的に、好きなだけ多くの借用者に貸与することができます。
  3. そのリソースを、変更可能的に、あるひとつの借用者に貸与することができます。

しかし、これには幾つかの制約が伴います:

  1. もし誰かがあなたのリソースを(変更可能的にであれ変更不能的にであれ)借用しているなら、あなたはそのリソースを変更することも、それをほかの誰かに変更可能的に貸与することも、できません。

  2. もし誰かがあなたのリソースを変更可能的に借用しているならば、あなたはそれを(変更可能的にであれ変更不能的にであれ)いっさい貸し出すことができませんし、どんなやり方であれそれにアクセスしてはいけません。

この「貸与」とか「借用」ってのはなんのことなんでしょうか? メモリを割当すると、そのメモリへのポインタが手に入ります。このポインタによって、あなたは当該のメモリを操作することが許されます。もしあなたがこのポインタの所有者ならば、このポインタを一時的に借用するもうひとつ別の束縛を作成することが許されます。そして、両者を用いてメモリを操作することができます。借用者があなたからポインタを借用しているその時間の長さのことを 生存期間 と言います。

もし2つの別個の束縛がポインタを共有しているとしても、そのポインタが指すメモリが変更不能ならば、なんの問題もありません。しかし、それが変更可能ならば、両方のポインタが同時にメモリに書き込もうとすることがあり得るわけで、 競合状況・レースコンディション が生じます。それゆえ、もし誰かがあなたから借用したものを変更しようとするなら、あなたはそのポインタをその他の誰にも貸出してはならないということになります。

Rustには 借用検査器 という洗練された仕組みが備わっており、みんながこのルールに従っていることを保証してくれます。それによって、これらのルールがどれも破られていないということを、コンパイル時に検証するのです。もし問題がなければ、プログラムはコンパイルに成功し、この件では実行時オーバーヘッドは何も生じません。借用検査器はコンパイル時にのみ動作します。もし借用検査器によって問題が見つかると、 生存期間エラー が報告され、プログラムはコンパイルされてくれません。

すぐ飲み込むには量が多いですよね。これはRust全体でも最も重要な概念のひとつでもあります。実際にこの構文が使われるところを見てみましょう:

{
    let x = 5i; // x はこの整数の所有者であり、この整数はスタック上のメモリにあります。

    // ここにはほかのコードが来ます……

} // 特権その1: x がスコープを外れると、このメモリは解放されます。


/// この函数は整数を借用します。
/// これはこの函数が呼び出し元に返る時に自動的に返却されます。
fn foo(x: &int) -> &int { x }

{
    let x = 5i; // xはこの整数の所有者であり、この整数はスタック上のメモリにあります。

    // 特権その2: あなたはそのリソースを好きなだけ多くの借用者に貸与できます。
    let y = &x;
    let z = &x;

    foo(&x); // 函数でも借用できます!

    let a = &x; // いつでもずっとこういうことができます。
}

{
    let mut x = 5i; // xはこの整数の所有者であり、この整数はスタック上のメモリにあります。

    let y = &mut x; // 特権その3: あなたはそのリソースを、変更可能的に、
                    // 単一の借用者に貸与することができます。
}

もしあなたが借用者側だとしても、同じように幾つかの特権が得られるのですが、ある制限にも従わなければなりません:

  1. もし借用が変更不能のものならば、そのポインタが指すデータを読み出すことができます。
  2. もし借用が変更可能のものならば、そのポインタが指すデータを読み出し、書き込むことができます。
  3. そのポインタを誰か他の人に貸与することもできますが、 しかし
  4. その場合には、相手はあなたが自分自身の借用を返却する前に、それを返さなければなりません。

この最後の要求は奇妙なものに見えるかもしれませんが、意味はわかりますよね。あなたがもし何かを返却しようとするときに、それを誰かに貸してしまっているならば、あなたがそれを元に返すためにはまずその人からあなたにそれを返してもらう必要があります。もしそうしないと、所有者がメモリを解放してしまったときに、我々がそれを貸し出した相手の人は無効なメモリへのポインタを持っていることになります。これを「宙に浮いたポインタ dangling pointer」と言います。

この一連の話が始まったそもそものエラーをもう一度見てみましょう。それは、あるものを変更可能的に貸与する所有者に課せられる制限に対する違反でした。コードはこうでしたね:

let mut x = 5i;
let y = &mut x;
let z = &mut x;

エラーはこうでした:

error: cannot borrow `x` as mutable more than once at a time
     let z = &mut x;
                  ^
note: previous borrow of `x` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `x` until the borrow ends
     let y = &mut x;
                  ^
note: previous borrow ends here
 fn main() {
     let mut x = 5i;
     let y = &mut x;
     let z = &mut x;
 }
 ^

このエラーは3つの部分からなっています。それぞれ順に見ていきましょう:

error: cannot borrow `x` as mutable more than once at a time
     let z = &mut x;
                  ^

このエラーは制限について述べています:変更可能なものを同時に複数回貸し出すことはできません。借用検査器はルールをしっかり承知しています!

note: previous borrow of `x` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `x` until the borrow ends
     let y = &mut x;
                  ^

コンパイルエラーの中には、エラーを直すのを助けるための注記が附されているものがあります。このエラーには2つの注記が伴っており、これがその第1です。この注記は、最初の変更可能的な借用が生じたその正確な場所を教えてくれています。エラーのほうでは、2番目の借用が表示されていました。というわけで、この問題が生じている両方の部分がわかったわけです。また、この注記は規則#3にも言及しており、借用が終了するまではxを変更できないよ、ということを我々に思い出させてくれています。

note: previous borrow ends here
 fn main() {
     let mut x = 5i;
     let y = &mut x;
     let z = &mut x;
 }
 ^

これが第2の注記です。これは、最初の借用がどこで終わることになっているかを知らせてくれています。この情報は有益なものです。借用が終わるまでxを借用するのを待てば問題なくうまくいくわけですからね。

更に発展的なパターンについては生存期間ガイドを参照してください。'a構文を伴った次のような型シグナチャが何を意味しているかもそこで知ることができます:

pub fn as_maybe_owned(&self) -> MaybeOwned<'a> { ... }

17.3. ボックス

ここまでのところ、参照はどれもスタック上に作成された変数に対するものでした。Rustではヒープに変数を割当する最も簡単な方法は、 ボックス を使うというものです。ボックスを作成するには、boxというキーワードを使います:

let x = box 5i;

これは、ヒープに整数値5を割当して、それを参照する束縛xを作成しています。ボックス化されたポインタの凄いところは、この割当を手動で解放する必要がない、ということです! もし次のように書いたとしたら:

{
    let x = box 5i;
    // ここで用事をする
}

Rustはこのブロックの終端で自動的にxを解放します。これはRustにガーベジコレクタがあるからではありません --- 実際そんなものはないのですから。そうではなくて、xがスコープを外れると、Rustはxを解放するのです。このRustコードは以下の様なC言語のコードを同じことをしています:

{
    int *x = (int *)malloc(sizeof(int));
    // ここで用事をする
    free(x);
}

これはつまり、我々が手動によるメモリ管理の恩恵を受けつつも、間違ったことをしないようにコンパイラが保証してくれているということです。メモリを解放することを忘れるということがあり得ないのです。

ボックスはその中身の唯一の所有者になります。ですから、それに対する変更可能な参照を取ってから、その後に元々のボックスを使う、ということはできません。つまり:

let mut x = box 5i;
let y = &mut x;

*x; // 5 だと思うかもしれませんが、これは実際にはエラーになります

こうすると次のようなエラーになります:

8:7 error: cannot use `*x` because it was mutably borrowed
 *x;
 ^~
 6:19 note: borrow of `x` occurs here
 let y = &mut x;
              ^

yが中身を借用している限り、xを使うことはできません。yが値の借用を終えてからならば、それを再び使うことができます。ですから次のコードは問題なく動作します:

let mut x = box 5i;

{
    let y = &mut x;
} // y はこのブロックの最後でスコープを外れます

*x;

17.4. RcArc

ときには、ヒープに何かを割当しつつ、しかしながらそのメモリに対する複数の参照を取る必要があるということもあります。RustのRc<T>(アール・シー・ティーと読みます)とArc<T><T>はジェネリクスを表していますがそれについては後に触れます)という2つの型がそのような能力を提供しています。Rcは「参照カウントされた reference counted」ということを意味しており、Arcは「アトミックに参照カウントされた atomically reference counted」ということを意味しています。複数の所有者を追跡するRustの方法は次のようなものです。まず、Rc<T>に対する新しい参照が作成されるそのたびごとに、その内部の「参照カウント」をひとつ増やします。参照がスコープを外れるそのたびごとにカウントをひとつ減らします。カウントがゼロになったとき、Rc<T>は安全に解放することができます。Arc<T>Rc<T>と殆ど一緒ですが、ひとつだけ違いがあります。Arcの「アトミックに」というのは、参照カウントの増減がスレッドセーフなメカニズムによって行われるということを意味しています。なぜまた、わざわざ型を2つ用意するのでしょうか? それはRc<T>の方が速いからで、もしマルチスレッドを扱っているのでなければ、その利点を活用できるからです。Rustでのスレッド処理についてはまだお話していませんから、この節ではRc<T>について見てみましょう。

Rc<T>を作成するにはRc::new()を使います:

use std::rc::Rc;

let x = Rc::new(5i);

もうひとつ別に参照を作成するには.clone()メソッドを使います:

use std::rc::Rc;

let x = Rc::new(5i);
let y = x.clone();

Rcは、参照のいずれかが生きている限り生存します。参照が全てスコープを外れてしまったならば、その後でメモリが解放されます。

もしRc<T>Arc<T>を使うならば、循環参照を持ち込まないように注意しなければなりません。もしお互いを指すような2つのRc<T>があると、両者の参照カウントは決してゼロにならないので、メモリリークを引き起こすことになります。これについて更に知りたければ、ポインタ・ガイドのRc<T>Arc<T>の節を参照してください。

18. パターン

このガイドでは既に何回かパターンを使ってきました。最初は、let束縛のところで、それからmatch文のところで、使いましたね。さて、次に続けて、パターンにできることの全てを概観する弾丸ツアーといきましょう!

手早く復習しましょう。リテラル値に直接マッチさせることができ、_は「なんでも」なケースとして働くのでした:

let x = 1i;

match x {
    1 => println!("one"),
    2 => println!("two"),
    3 => println!("three"),
    _ => println!("anything"),
}

|を使えば複数のパターンに対してマッチさせることができます:

let x = 1i;

match x {
    1 | 2 => println!("one or two"),
    3 => println!("three"),
    _ => println!("anything"),
}

ある範囲の値にマッチさせには...が使えます:

let x = 1i;

match x {
    1 ... 5 => println!("one through five"),
    _ => println!("anything"),
}

この範囲指定はもっぱら整数と単一の文字に対して用います。

|...によって複数のものとマッチさせる場合、@を使ってその値を名前に束縛できます:

let x = 1i;

match x {
    x @ 1 ... 5 => println!("got {}", x),
    _ => println!("anything"),
}

ヴァリアントを持っている列挙体に対してマッチを行っている場合には、..を使ってヴァリアントの中の値を無視することができます:

enum OptionalInt {
    Value(int),
    Missing,
}

let x = Value(5i);

match x {
    Value(..) => println!("Got an int!"),
    Missing   => println!("No such luck."),
}

ifを使って、 マッチのガード部 を設けることもできます:

enum OptionalInt {
    Value(int),
    Missing,
}

let x = Value(5i);

match x {
    Value(x) if x > 5 => println!("Got an int bigger than five!"),
    Value(..) => println!("Got an int!"),
    Missing   => println!("No such luck."),
}

ポインタに対してマッチする場合にも、ポインタを宣言するときと同じ構文が使えます。まず最初は&から:

let x = &5i;

match x {
    &x => println!("Got a value: {}", x),
}

この場合、matchの内部のxint型になります。言い換えると、パターンの左辺はポインタ値を構造分解してくれています。&5iというポインタがあれば、&xの中でのx5iになります。

参照が欲しいなら、refというキーワードを使います:

let x = 5i;

match x {
    ref x => println!("Got a reference to {}", x),
}

この場合、matchの内部のx&int型になります。言い換えれば、refというキーワードによって、パターン内で使うための参照を作成しているのです。もし変更可能な参照が必要ならばref mutが同じように使えます:

let mut x = 5i;

match x {
    ref mut x => println!("Got a mutable reference to {}", x),
}

構造体の場合なら、パターンの内部でそれを構造分解することができます:

struct Point {
    x: int,
    y: int,
}

let origin = Point { x: 0i, y: 0i };

match origin {
    Point { x: x, y: y } => println!("({},{})", x, y),
}

もし値の中の特定のものについてだけ気にしているのであれば、全部に名前を与える必要はありません:

struct Point {
    x: int,
    y: int,
}

let origin = Point { x: 0i, y: 0i };

match origin {
    Point { x: x, .. } => println!("x is {}", x),
}

ヒュ~! マッチを行うのには本当に沢山の違ったやり方がありますね。しかも、したいことに応じて、これをみんな混ぜ合わせてマッチさせることができるのです:

match x {
    Foo { x: Some(ref name), y: None } => ...
}

パターンというやつは実に強力です。巧く活用してくださいね。

19. メソッド構文

函数というのは実に結構なものですが、なにかあるデータに対してひとくさり函数を呼びだそうとすると、不便なことになることがあります。次のようなコードを考えてみてください:

baz(bar(foo(x)));

これを左から右に読んでしまって、「bazしてbarしてfoo」に見えると思います。しかし、これは函数が呼び出される順番ではありません。正しい順番は内側から外側で「fooしてbarしてbaz」なのです。こうではなくて次のようにできたらいいと思いませんか?

x.foo().bar().baz();

運の良いことに、みなさんがこの誘導尋問に沿ってそう思っただろう通り、実際にそうすることができます! Rustはimplというキーワードによって、この メソッド呼出し構文 を使うことができるようにしてくれます。

使い方はこういう感じです:

struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}

impl Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * (self.radius * self.radius)
    }
}

fn main() {
    let c = Circle { x: 0.0, y: 0.0, radius: 2.0 };
    println!("{}", c.area());
}

これで12.566371と表示されます。

まず円を表現する構造体を作りました、それからimplブロックを書き、その内部でareaというメソ度を定義しています。メソッドは引数の最初のところに&selfという特別なパラメータを取ります。これにはself&self&mut selfという3つの異型があります。1番目にくるこのパラメータはx.foo()xだと考えてください。3つの異型はxがとりうる可能性のあるものの種類に対応しています。xが単にスタック上の値ならselfで、参照なら&selfで、変更可能な参照ならば&mut selfです。デフォルトでは&selfを使うことになっています。これはそれが一番普通だからですね。

最後のところ、多分覚えてると思いますが、円の面積の値はπ*r²でしたよね。areaに対して&selfというパラメータを受け取り、これを他のパラメータとまったく同様に使うことができます。これがCircleだということはわかっていますから、他の構造体の時にするのとまったく同じようにradiusにアクセスすることができます。その後に、πのインポートと乗算が来て、これで面積が得られました。

selfパラメータを取らないメソッドを定義することもできます。以下はRustのコードでごく頻繁に用いられるパターンです:

struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}

impl Circle {
    fn new(x: f64, y: f64, radius: f64) -> Circle {
        Circle {
            x: x,
            y: y,
            radius: radius,
        }
    }
}

fn main() {
    let c = Circle::new(0.0, 0.0, 2.0);
}

この静的メソッドは、新しいCircle値をひとつ作成します。[* selfを受け取らない ] 静的メソッドはref.method()という構文ではなく、Struct::method()という構文で呼び出されることに注意してください。

20. クロージャ

ここまで、Rustでたくさんの函数を作成してきました。その全部に名前をつけてきましたよね。でも、Rustでは無名関数を作成することが許されています。Rustの無名関数は クロージャ と呼ばれます。クロージャは単独では大して面白いものではありませんが、クロージャを引数に取る函数と組み合わせると、実にパワフルなことができるようになります。

クロージャを作成してみましょう:

let add_one = |x| { 1i + x };

println!("The sum of 5 plus 1 is {}.", add_one(5i));

|...| { ... }という構文を使ってクロージャを作成し、あとでこれを利用できるように束縛を作成します。普通の名前付き函数に対してするのとまったく同様に、束縛名と両括弧を使ってこの函数を呼び出していることに注意してください。

構文を比較してみましょう。両者は大変に似通っています:

let add_one = |x: int| -> int { 1i + x };
fn  add_one   (x: int) -> int { 1i + x }

気づいているかもしれませんが、クロージャの場合には引数と返値の型が推論されますので、わざわざ型を宣言する必要はありません。これは名前付き函数と違う点で、名前付き函数の場合にはデフォルトでユニット型()を返すのでしたね。

クロージャと名前付き函数の間にはひとつ大きな違いがあります。それはその名称にあります。クロージャというのは「環境を閉包(close)」するものなのです。それはまたどういう意味でしょう? こういう意味です:

fn main() {
    let x = 5i;

    let printer = || { println!("x is: {}", x); };

    printer(); // prints "x is: 5"
}

||という構文は、これが引数を取らない無名クロージャであることを示しています。これがなければ、単に{}に囲まれたコードのブロックと同じことになります。

言い換えれば、クロージャはそれが定義されているスコープ内の変数にアクセスすることができるのです。ですから、クロージャはそれが用いている変数をいずれも借用することになります。以下のコードはエラーになります:

fn main() {
    let mut x = 5i;

    let printer = || { println!("x is: {}", x); };

    x = 6i; // error: cannot assign to `x` because it is borrowed
}

20.1. プロック

Rustには プロック proc と呼ばれるもうひとつ別のタイプのクロージャがあります。プロックはprocというキーワードで作成します:

let x = 5i;

let p = proc() { x * x };
println!("{}", p()); // prints 25

プロックにはクロージャとひとつ大きな違いがあります。プロックは一度しか呼び出すことができません。ですから、以下のコードをコンパイルしようとするとエラーになります:

let x = 5i;

let p = proc() { x * x };
println!("{}", p());
println!("{}", p()); // error: use of moved value `p`

この制限は重要です。プロックはそれが捕捉した値を消費してしまうことが許されており、健全性を担保するために、その呼び出しが一度だけに制限されなければならないのです。消費されてしまった値は再度の呼び出し時には無効になってしまっているでしょう。

プロックがもっとも有用になるのはRustの並行性に関する機能を使うときです。ですから、いまのところは措いておきましょう。このガイドの「タスク」の節で、それについては更にお話をします。

20.2. クロージャを引数として受け取る

クロージャは他の函数の引数になるときに、大変に有用になります。以下がその例です:

fn twice(x: int, f: |int| -> int) -> int {
    f(x) + f(x)
}

fn main() {
    let square = |x: int| { x * x };

    twice(5i, square); // evaluates to 50
}

この例を分解して見てみましょう。まずmainから始めます:

let square = |x: int| { x * x };

これは前にも見ましたね。整数を取ってその平方を返すクロージャを作成しています。

twice(5i, square); // evaluates to 50

この行は実に興味深いですね。ここでは、twiceという函数を呼び出し、それに5という整数とsquareというクロージャを引き渡しています。函数に2つの変数束縛を引き渡す他の場合とまったく同じですが、クロージャを使ったことがないとちょっと複雑に思えるかもしれません。単に「2つの変数を渡しているところで、一方はint値で他方は函数だ」と考えてください。

次にtwiceがどう定義されているか見てみましょう:

fn twice(x: int, f: |int| -> int) -> int {

twicexfという2つの引数を取っています。だから、さきほど2つの引数でtwiceを呼び出したのでした。xint型の値で、これはいままでにもうウンザリするほどやってきました。しかしながら、fの方は函数で、この函数はintを取ってintを返します。|int| -> intという構文が、上で見たsquareの定義に返値の型を中に付け加えると、よく似ていることに注意してください:

let square = |x: int| -> int { x * x };
//           |int|    -> int

この函数はintを取ってintを返します。

これはいままで見た中でもっとも込み入った型シグナチャですね! どうなっているのかを理解できるまで何回か読み返してください。ちょっとばかり練習はいりますが、慣れれば簡単です。

また、最後にtwiceintを返しています。

大丈夫ですね。ではtwiceの本体を見てみましょう:

fn twice(x: int, f: |int| -> int) -> int {
  f(x) + f(x)
}

クロージャにはfという名前がついていますから、以前にクロージャを呼び出したのとまったく同様にそれで呼び出しをすることができます。そして引数xをそれぞれに引き渡しています。というわけで、「twice」という名前になっています。

算数をやってみれば、(5 * 5) + (5 * 5) == 50ですよね。というわけで、それが得られる出力になります。

すっかり慣れるまで、この概念をいじくり回してみてください。Rustの標準ライブラリは、そうすることが適切である場合には、クロージャを多用しています。ですからあなたも、このテクニックをたくさん使うことになります。

もしsquareにわざわざ名前を付けたくなかったら、インラインで定義をすることもできます。次の例は先程のものと同じものです:

fn twice(x: int, f: |int| -> int) -> int {
    f(x) + f(x)
}

fn main() {
    twice(5i, |x: int| { x * x }); // evaluates to 50
}

クロージャを使おうと思うところではどこでも、名前付き函数の名前を使うことができます。先ほどの例のもうひとつ別の書き方はこうなります:

fn twice(x: int, f: |int| -> int) -> int {
    f(x) + f(x)
}

fn square(x: int) -> int { x * x }

fn main() {
    twice(5i, square); // evaluates to 50
}

この書き方はあまり普通ではありませんが、時には有用なこともあります。

クロージャのコツを理解するのに必要なのはこれで全てです。クロージャは最初はちょっと奇妙に思えるかもしれませんが、いったん使い慣れると、クロージャがないどんな言語についてもそれを残念に思うようになります。函数を他の函数に渡すというのは信じられないくらい強力なのです。次に、実際にそういうもののひとつを見てみましょう。それがイテレータです。

21. イテレータ

ループについてお話しましょう。

Rustのforループを覚えていますか? 以下がその例です:

for x in range(0i, 10i) {
    println!("{:d}", x);
}

以前よりRustについてわかるようになったので、これがどう動作しているかの詳細についてお話することができます。ここでのrange函数は 反復子・イテレータ というものを返しているのです。イテレータというのは、.next()メソッドを繰り返し呼び出し続けられるようなもので、なにかのシークエンスを提供してくれるもののことです。

こんな感じです:

let mut range = range(0i, 10i);

loop {
    match range.next() {
        Some(x) => {
            println!("{}", x);
        },
        None => { break }
    }
}

rangeの返値、つまりイテレータに対する変更可能な束縛を作成しています。それからloopし、その中ではmatchを使っています。このmatchrange.next()の結果に対して使われていますが、このメソッドはイテレータの次の値に対する参照を返してくるものです。nextOption<int>を返します。この場合には、もし次の値が存在するならSome(int)を、もし値を使いきってしまっていればNoneを返してきます。Some(int)が得られた場合にはそれを表示し、Noneの場合にはループから脱出します。

このコード例は基本的にはforループのヴァージョンと同一のものです。forループは、このloop/match/breakの構造を書くための便利な記法に過ぎません。

しかしながら、イテレータを利用しているのはforループだけではありません。自作のイテレータを書く際には、Iteratorトレイトを実装することになります。このガイドの範囲を超える事項になってしまいますが、Rustでは様々な仕事を達成するための有用なイテレータが多数提供されています。その話をする前に、Rustに於けるアンチパターンの話をしなければいけません。そう、それはrangeそのものです。

ええ、ええ、rangeがどんだけカッコイイかをお話したばかりですよね。でも、rangeはとても原始的なものに過ぎません。ベクターの中身を渡るような反復が必要な場合に、次のように書きたくなるでしょう:

let nums = vec![1i, 2i, 3i];

for i in range(0u, nums.len()) {
    println!("{}", nums[i]);
}

これは実際に行われているイテレータの使い方に比べると、厳密に劣ったやり方です。実際的には、ベクターの.iter()メソッドが、ベクターの各要素に対する参照を最初から最後まで順番に反復してくれるイテレータを返してくれます。ですから、こう書きます:

let nums = vec![1i, 2i, 3i];

for num in nums.iter() {
    println!("{}", num);
}

こう書くべき理由が2つあります。第1に、これが我々の意図するところをより直截に表現しているということです。我々はあくまでベクターの全体に対して反復を行いたいのであって、インデックスに対して反復を行いつつその上でこのインデックスでベクターを参照する、ということがしたいのではありません。第2にこちらのヴァージョンの方が効率的です。最初のヴァージョンはnums[i]を使っているために余計な境界チェックが必要です。これに対して、後者の場合には、イテレータによって順番に各要素への参照を生成していますから、境界チェックは行われていません。これはイテレータを使うときには普通のことです。不要な境界チェックを無視しつつ、それでもなお安全だとわかっている、ということができるわけです。
もうひとつ、println!の動作の仕方のせいで、必ずしも100%明瞭だというわけではない詳細がここに関わっています。numは実際には&int型です。つまり、あくまでもintへの参照であって、intそれ自体ではありません。println!は我々に代わって参照解決をしてくれるので、表面上はそれが見えなくなっています。こういうコードもきちんと動作します:

let nums = vec![1i, 2i, 3i];

for num in nums.iter() {
    println!("{}", *num);
}

今回は、numを明示的に参照解決しています。iter()が参照を返すのはなんででしょう? うむ、もしデータそれ自体を返されてしまうとしたら、我々はそれの所有者にならなければならず、そのためにはこのデータを複製してその複製の方を返す羽目になるからです。参照を使えば、データへの参照を借用すれば済みます。だから、単に参照を渡すだけで、複製を行う必要はありません。
というわけで、多くの場合にrangeは欲しいものではない、ということはきちんと確認できました。では代わりに何があればいいのかということをお話しましょう。
ここで、 イテレータと、 イテレータ・アダプタコンシューマ という3つの広範なクラスが話に関係してきます。定義はこうです:
「イテレータ」は値のシークエンスを提供します。
「イテレータ・アダプタ」は、イテレータに対して作用し、異なった出力シークエンスを持つ、新たなイテレータを創り出します。
「コンシューマ」は、イテレータに対して作用し、なんらかのある最終的な値の集合を創り出します。
まず初めにコンシューマについてお話しましょう。というのも、既にイテレータについてはrange
を見ましたから。

21.1. コンシューマ

「コンシューマ」はイテレータに対して作用し、何らかの種類の値をひとつないし複数返します。もっともありふれたコンシューマはcollect()です。次のコードはコンパイルできませんが、しかし意図は伝わるでしょう:

let one_to_one_hundred = range(0i, 100i).collect();

見ての通り、イテレータに対してcollect()を呼び出しています。collect()はイテレータが返してくるちょうどそれだけの分の値を取り、その結果の集合を返します。で、なんでコンパイルできないんでしょう? それは、あなたがcollectしてどんな型のものを手に入れたいかがRustにはわからないからです。ですから、それをRustに知らせてやる必要があります。次のヴァージョンならコンパイルできます:

let one_to_one_hundred = range(0i, 100i).collect::<Vec<int>>();

もしちゃんと覚えてるなら、::<>構文で型ヒントを与えることができるのでしたね。ですから、このようにしてRustに整数のベクターが欲しい、と伝えています。

collect()は最もありふれたコンシューマですが、コンシューマは他にもあります。find()なんかがそうです:

let greater_than_forty_two = range(0i, 100i)
                             .find(|x| *x >= 42);

match greater_than_forty_two {
    Some(_) => println!("We got some numbers!"),
    None    => println!("No numbers found :("),
}

findはクロージャを取り、イテレータの各要素への参照に対して作用します。このクロージャは、もしイテレータの要素が探している要素であれば真を返し、そうでなければ偽を返します。マッチする要素が見つからないかもしれないので、findは要素それ自体ではなくあくまでOptionを返すことになっています。

もうひとつ重要なコンシューマとしてfoldがあります。このような感じです:

let sum = range(1i, 100i)
              .fold(0i, |sum, x| sum + x);

foldfold(base, |accumulator, element| ...)という形式を取ります。まず、2つの引数をとりますが、第1引数は「基底値 base」と呼ばれる要素になります。第2引数はクロージャで、このクロージャはそれ自体が2つの引数を取ります。クロージャの第1引数は「蓄積引数 accumlator」で、第2引数が「要素値 element」になります。各回の反復ごとに、このクロージャが呼び出され、その結果が次の反復回の蓄積引数の値になります。反復の初回には、基底値が蓄積引数の値になります。

うむ、ちょっとこんがらがりますよね。上で出てきたイテレータについて、全部の項目の値を調べてみましょう。

次のような引数の組み合わせで、上で出てきたイテレータnums.iter() に対してfold()を呼び出したとしましょう:

nums.iter().fold(0i, |sum, x| sum + x);

まず、0iが基底値です。sumは蓄積引数で、xが要素値です。反復の初回には、sumを基底値0iにセットします。xnumsの最初の要素ですから、つまり1iです。それからsumxを加算します。そうすると0i + 1i = 1iになります。反復の2回目には、この値が蓄積引数sumにセットされます。要素値は配列の第2要素、つまり2iになります。1i + 2i = 3iというわけで、これが反復の最終回の蓄積引数の値になります。この最終回の反復では、xは最後の値3iになり、3i + 3i = 6iというわけで、これがsumにセットされて最後の結果になります。1 + 2 + 3 = 6というわけで、これが得られる結果です。

反復回 基底値 蓄積引数 要素値 クロージャの結果
1回目 0i 0i 1i 1i
2回目 0i 1i 2i 3i
3回目 0i 3i 3i 6i

ヒュ~。 最初の何回か見た限りではfoldはちょっと奇妙に見えるかもしれません。でもいったんピンとくれば、いたるところでfoldを使うことができます。なにかのリストがあって、なにかある単一の結果が欲しい、という時にはfoldが適任です。

コンシューマは、イテレータが持っている、でもまだそれについてお話していないもうひとつ別の性質、つまり遅延性(laziness)というもののお蔭で、重要になってきます。そこで、イテレータについてもう少しお話しましょう。そうすれば、なぜコンシューマが重要なのかがわかるでしょう。

21.2. イテレータ

前にも言ったように、イテレータというのは.next()メソッドを繰り返して呼び出せるようなもののことで、それによってなにかのシークエンスを提供するようなもののことです。メソッドを呼び出す必要があるわけですから、このことはイテレータが 遅延的 lazy であるということを意味します。たとえば次のコードは、実際には1-100の数値を生成するわけではなく、あくまで数のシークエンスを表現するような値を作成しているに過ぎません。

let nums = range(1i, 100i);

このrangeを使って何もしていないので、数のシークエンスは実際には生成されていません。ここでひとたびコンシューマを付け加えると:

let nums = range(1i, 100i).collect::<Vec<int>>();

こんどはcollect()によってrange()が値を供給することが要求されるので、このイテレータはシークエンスを実際に生成するという仕事をします。

rangeはみなさんが目にする2つの基本的なイテレータのひとつです。もうひとつがiter()で、これは前にも使いましたね。iter()はベクターを、そのベクターの各要素を順番に供給する単純なイテレータに転換するのでした:

let nums = [1i, 2i, 3i];

for num in nums.iter() {
   println!("{}", num);
}

この2つのイテレータは充分に役にたってくれるはずです。これよりもっと高度なイテレータもあって、これには要素が無限であるようなものが含まれています。たとえば次のcountです:

std::iter::count(1i, 5i);

このイテレータは1から始めて各回に5を加算しながらカウントアップを行っていきます。各回ごとに新しい整数を、永遠に供給します。ええまあ、技術的にはint型が表現できる最大の数までということになりますが。しかし、イテレータは遅延的ですから、これでなんの問題もありません! とはいっても、みなさんだってこれに対してcollect()を使ったりはしたくないでしょうけれども……

イテレータについてはこれで充分です。イテレータに関連してお話する必要がある最後の概念が、イテレータ・アダプタです。さあ、はじめましょう!

21.3. イテレータ・アダプタ

イテレータ・アダプタは、イテレータをとって、何らかの方法でそれに修正を加え、新たなイテレータを創り出します。最も単純な例がmapです:

range(1i, 100i).map(|x| x + 1i);

mapはイテレータに対して呼び出され、新しいイテレータを生成します。このイテレータの各要素の参照は、mapの引数として与えられたクロージャを元のイテレータの対応する要素に対して呼び出します。というわけで、2-101の数が得られることになります。うんまあ、きっとそのはずです! 実際には、この例をコンパイルすると次のような警告が出ます:

2:37 warning: unused result which must be used: iterator adaptors are lazy and
              do nothing unless consumed, #[warn(unused_must_use)] on by default
 range(1i, 100i).map(|x| x + 1i);
 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

またもや遅延性です! このクロージャはまったく実行されません。なので、次の例ではどんな数も表示されません:

range(1i, 100i).map(|x| println!("{}", x));

もし、イテレータに対してあるクロージャを副作用が目当てで実行しようとしているなら、その代わりに単なるforを使ってください。
興味を惹くようなイテレータ・アダプタが大量にあります。たとえばtake(n)はイテレータから最初のn個の項目を引き出して、それをリストとして返します。先ほどの無限イテレータcount()で試してみましょう:

for i in std::iter::count(1i, 5i).take(5) {
    println!("{}", i);
}

これで、次のように表示されます:

1
6
11
16
21

filter()はクロージャを引数に取るようなアダプタです。このクロージャは真か偽を返します。filter()が創りだす新たなイテレータは、このクロージャが真を返すような要素のみを生成するものになります:

for i in range(1i, 100i).filter(|x| x % 2 == 0) {
    println!("{}", i);
}

このコードは1から100 の間の全ての偶数を表示します。
これらは全てまとめて連鎖させることができます。何回かアダプタを適用してから、結果にコンシューマを適用します。試してみましょう:

range(1i, 1000i)
    .filter(|x| x % 2 == 0)
    .filter(|x| x % 3 == 0)
    .take(5)
    .collect::<Vec<int>>();

このコードは6, 12, 18, 24, 30を含むベクターを返します。

ここでは、イテレータ、イテレータ・アダプタ、コンシューマ、で何ができるかについてちょっぴり齧ったに過ぎません。実際に有用なイテレータが他にもたくさんありますし、自作の物も書くことができます。イテレータはあらゆる種類のリストを操作するための安全で効率的な方法です。最初はちょっと見慣れないかもしれませんが、いじっているうちにやみつきになります。様々なイテレータとコンシューマの完全な一覧については、イテレータ・モジュールのドキュメントを参照してください。

22 ジェネリクス

函数とかデータ型を書いている時に、それらが複数の型の引数に対して動作してくれたらいいのに、と思うことがしばしばあります。たとえば、前に出てきたOptionalInt型を覚えていますか?

enum OptionalInt {
    Value(int),
    Missing,
}

もしOptionalFloat64型が欲しければ、新しく列挙体が必要になってしまいます:

enum OptionalFloat64 {
    Valuef64(f64),
    Missingf64,
}

これは実に不幸なことですよね。運の良いことに、もっと良いやり方を提供してくれる機能がRustにはあります。それがジェネリクス(総称型・総称函数)という機能です。型理論ではジェネリクスのことを パラメータ多相 parametric polymorphism と言います。これはジェネリクスが、ある所与のパラメータ上で(「パラメトリックな」)複数の形を取る(「ポリ」は複数のことで、「モルフ」は形のことです)型や函数であるということを意味しています。

ともあれ、型理論的な定義については措いておいて、OptionalIntを総称形式にするとどうなるか試してみましょう。実際のところ、Rust自身がこれを用意しています。こんな感じです:

enum Option<T> {
    Some(T),
    None,
}

<T>の部分はもうこれまでに何回か見てきましたが、この型が総称データ型であることを示しています。列挙体の宣言の内部で、Tに出会うたびに、それを総称型で用いられているのと同じ型で置き換えます。型註釈をもう少し追加した、Option<T>の使用法の例はこのような感じです:

let x: Option<int> = Some(5i);

型宣言の中でOption<int>と言っていますね。これがOption<T>と良く似ていることに注意してください。これはつまり、この特定のOption型については、Tintという値を持っているということです。束縛の右辺では、Some(T)という値を作成しています。ここではT5iですね。これはint値ですから、両辺はマッチし、Rustはご満悦です。もしこれがマッチしないとエラーになってしまいます:

let x: Option<f64> = Some(5i);
// error: mismatched types: expected `core::option::Option<f64>`
// but found `core::option::Option<int>` (expected f64 but found int)

これは、f64を保持できるようなOption<T>を作成できないということではありません。単に、両辺がマッチしないといけないというだけのことです:

let x: Option<int> = Some(5i);
let y: Option<f64> = Some(5.0f64);

これはまったく問題ありません。ひとたび定義すれば、複数の仕方で使えるのです。

ジェネリクスはあるひとつの型についてだけ総称的でなければならないというわけではありません。Rustが組み込みで用意しているResult<T, E>型を見てみましょう:

enum Result<T, E> {
    Ok(T),
    Err(E),
}

この型はTEという2つの型について総称的です。ところで、このTEという大文字はなんであれ好きな文字で構いません。もしそうしたければ、Result <T, E>は次のように定義することもできたのです:

enum Result<H, N> {
    Ok(H),
    Err(N),
}

命名規約では、最初の総称パラメータは「型 type」を表すTを使い、「エラー」を表す値についてはEを使うということになっていますが、別にそうでなくてもRustは気にはしません。

Result<T, E>型は、ある計算の結果を返すのに使われ、計算の結果が出なかった時にはエラーを返せるようにしようという意図のものです。使用例はこうです:

let x: Result<f64, String> = Ok(2.3f64);
let y: Result<f64, String> = Err("There was an error.".to_string());

この特定のResult型は、成功した時にはf64を返し、失敗した時にはStringを返します。Result<T, E>を使った函数を書いてみましょう:

fn inverse(x: f64) -> Result<f64, String> {
    if x == 0.0f64 { return Err("x cannot be zero!".to_string()); }

    Ok(1.0f64 / x)
}

ゼロの逆数を取るなんてことはしたくありませんから、ゼロが引き渡されていないことを確かめるためにチェックを行います。もしゼロが引き渡されたならErrをメッセージ付きで返します。もしそうでなくて大丈夫な値なら、Okを、答えとともに返します。

これに何の意味があるのか、ですか? ふむ、matchで網羅的マッチをするにはどうしたか覚えていますか? この函数は次のような使われ方をします:

let x = inverse(25.0f64);

match x {
    Ok(x) => println!("The inverse of 25 is {}", x),
    Err(msg) => println!("Error: {}", msg),
}

matchによって、Errの場合を処理することが強制されます。さらに、答えはOkに包まれていますから、matchを使わずに単に結果を使うということはできないのです:

let x = inverse(25.0f64);
println!("{}", x + 2.0f64); // error: binary operation `+` cannot be applied
           // to type `core::result::Result<f64,collections::string::String>`

この函数は大変結構ですが、もうひとつ問題があります。64bitの浮動小数点数の値にしか使えません。32bitの浮動小数点数も扱いたい場合にはどうするのでしょうか? それにはこう書かなければいけないでしょう:

fn inverse32(x: f32) -> Result<f32, String> {
    if x == 0.0f32 { return Err("x cannot be zero!".to_string()); }

    Ok(1.0f32 / x)
}

がっかりですね。ここで必要なのは総称的な函数です。幸いにして、そういう函数は書けますよね! しかしながら、それは今のところはまだうまく行かないのです。その話に踏み込む前に、どういう構文になるかについてお話しておきましょう。総称ヴァージョンのinverseはこんな感じになるはずです:

fn inverse<T>(x: T) -> Result<T, String> {
    if x == 0.0 { return Err("x cannot be zero!".to_string()); }

    Ok(1.0 / x)
}

Option<T>でそうだったのとちょうど同じように、inverse<T>にも同様の構文を使ってみます。そうすると、型シグナチャの残りの部分の中でTを使うことができます。xTという型を持ち、Resultの半分はTという型をもつことになります。しかしながら、この例をコンパイルしようとすると、エラーになります:

error: binary operation `==` cannot be applied to type `T`

Tはどんな型にもなりうるのですから、==を実装していない型にもなりうるわけです。したがって、最初の行がマズいということになるでしょう。でもどうしましょう?

この例の問題を修正するためには、Rustの機能をもうひとつ学ぶ必要があります。つまり、トレイトです。

23. トレイト

函数をメソッド構文で呼び出すために使われたimplというキーワードを覚えていますか?

struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}

impl Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * (self.radius * self.radius)
    }
}

トレイトはこれと同じようなものですが、トレイトについてはメソッドの型シグナチャだけを定義し、その後でその構造体について当のトレイトを実装するという順番になる、という点が違います。こんな感じです:

struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}

trait HasArea {
    fn area(&self) -> f64;
}

impl HasArea for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * (self.radius * self.radius)
    }
}

見ての通り、traitのブロックはimplのブロックに大変良く似ています。ただ、型シグナチャだけで、本体は定義していません。あるトレイトをimplする際には単にimpl Itemとするのではなくimpl Trait for Itemとします。

これがどうしたというんでしょうか? 総称的な逆数函数で生じていたエラーを覚えていますか?

error: binary operation `==` cannot be applied to type `T`

ここで、総称を制約するためにトレイトを使うことができます。次の函数を見てください。これはコンパイルできず、以前と同様のエラーになります:

fn print_area<T>(shape: T) {
    println!("This shape has an area of {}", shape.area());
}

Rustは次のように文句を言います:

error: type `T` does not implement any method in scope named `area`

Tはどんな型にもなりうるので、それがareaメソッドを実装しているかどうかが確かではないのです。しかしながら、総称的なTに対して トレイトによる制約 を付け加える事によって、それがareaメソッドを実装していることを保証することができます:

fn print_area<T: HasArea>(shape: T) {
    println!("This shape has an area of {}", shape.area());
}

<T: HasArea>は、HasAreaトレイトを備えている任意の型を意味する構文です。トレイトは函数の型シグナチャを定義するものですから、HasAreaを実装している任意の型について.area()メソッドが実装されていることが保証できます。

先ほどの例を拡張して、これがどういう動作をするか見てみましょう:

trait HasArea {
    fn area(&self) -> f64;
}

struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}

impl HasArea for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * (self.radius * self.radius)
    }
}

struct Square {
    x: f64,
    y: f64,
    side: f64,
}

impl HasArea for Square {
    fn area(&self) -> f64 {
        self.side * self.side
    }
}

fn print_area<T: HasArea>(shape: T) {
    println!("This shape has an area of {}", shape.area());
}

fn main() {
    let c = Circle {
        x: 0.0f64,
        y: 0.0f64,
        radius: 1.0f64,
    };

    let s = Square {
        x: 0.0f64,
        y: 0.0f64,
        side: 1.0f64,
    };

    print_area(c);
    print_area(s);
}

このプログラムの出力は:

This shape has an area of 3.141593
This shape has an area of 1

となります。見ての通り、print_areaはいまや総称的になっていますが、同時に、正しい型の引数を渡していることを保証しています。誤った型を渡すと:

print_area(5i);

コンパイル時エラーになります:

error: failed to find an implementation of trait main::HasArea for int

ここまでのところ、構造体についてだけトレイトの実装を追加してきたわけですが、実際には任意の型についてトレイトを実装することができます。つまり、技術的にはintHasAreaを実装することも、その気になればできるのです:

trait HasArea {
    fn area(&self) -> f64;
}

impl HasArea for int {
    fn area(&self) -> f64 {
        println!("this is silly");

        *self as f64
    }
}

5i.area();

可能ではあるのですが、こういったプリミティブな型にメソッドを実装するのはまずいスタイルです。

ここには広大な未開拓地が広がっているように思うかもしれませんが、トレイトには我々の制御を離れてしまうことを阻止するような制限があと2つあります。まず、トレイトはそのトレイトのメソッドを使用したいと思うそのスコープでuseされなければなりません。ですから、たとえば次のような例は動作しません:

mod shapes {
    use std::f64::consts;

    trait HasArea {
        fn area(&self) -> f64;
    }

    struct Circle {
        x: f64,
        y: f64,
        radius: f64,
    }

    impl HasArea for Circle {
        fn area(&self) -> f64 {
            consts::PI * (self.radius * self.radius)
        }
    }
}

fn main() {
    let c = shapes::Circle {
        x: 0.0f64,
        y: 0.0f64,
        radius: 1.0f64,
    };

    println!("{}", c.area());
}

さて、この例では構造体とトレイトをモジュールの内部に移動してしまいましたが、そうするとエラーになります:

error: type `shapes::Circle` does not implement any method in scope named `area`

mainのすぐ上にuseの行を付け加えて、適切なものをpublicにすれば、何の問題もありません:

use shapes::HasArea;

mod shapes {
    use std::f64::consts;

    pub trait HasArea {
        fn area(&self) -> f64;
    }

    pub struct Circle {
        pub x: f64,
        pub y: f64,
        pub radius: f64,
    }

    impl HasArea for Circle {
        fn area(&self) -> f64 {
            consts::PI * (self.radius * self.radius)
        }
    }
}


fn main() {
    let c = shapes::Circle {
        x: 0.0f64,
        y: 0.0f64,
        radius: 1.0f64,
    };

    println!("{}", c.area());
}

この制約の意義は、もし誰かがintにメソッドを追加するというようなことをしてしまっても、あなた自身がそのトレイトをuseしない限り、影響がないということにあります。

トレイトの実装にはもうひとつ制限があります。あなたがimplを書いているトレイトや型は、自作ののクレイト内に置かれなければなりません。HasAreaトレイトは自作のクレイト内にありますから、intHasAreaを実装することはできます。しかし、intについてFloatを実装しようとしても、Rust自体がこのトレイトを提供していますので、トレイトと対象の型の双方を自作のクレイト内に置くことはできません。

トレイトについて最後にもうひとつ。トレイトの束縛を受けた総称函数は 単相化 monomorphization ("mono"は「単一の」、"morph"は「形」を意味します)というものを使用し、それによって、静的にディスパッチされます。って、どういう意味でしょう? ふむ、もういちどprint_areaを見てみましょう:

fn print_area<T: HasArea>(shape: T) {
    println!("This shape has an area of {}", shape.area());
}

fn main() {
    let c = Circle { ... };

    let s = Square { ... };

    print_area(c);
    print_area(s);
}

このトレイトをCircleSquareに対して使うと、Rustは最終的にそれぞれの具体型について2つの函数を生成し、函数呼び出しの箇所をこの具体化された実装で置き換えます。言い換えれば、こんな感じになるのです:

fn __print_area_circle(shape: Circle) {
    println!("This shape has an area of {}", shape.area());
}

fn __print_area_square(shape: Square) {
    println!("This shape has an area of {}", shape.area());
}

fn main() {
    let c = Circle { ... };

    let s = Square { ... };

    __print_area_circle(c);
    __print_area_square(s);
}

これは単に説明のためで、函数の名前が実際にこう置換されるわけではありません。しかし、見ての通り、どちらのヴァージョンを呼び出すべきかを決める際のオーヴァーヘッドはここには存在しません。そのため「静的にディスパッチされる」と言っているわけです。この方式の欠点は、同じ函数の複製が2つできてしまうので、バイナリが少しばかり大きくなってしまうということです。

24. タスク

並行性(concurrency)と並列性(parallelism)は広範なソフトウェア開発者にとって、ますます興味深いものとなりつつあります。現代のコンピュータはマルチコアであることが多く、携帯電話のような組み込みデバイスまでもが複数のプロセッサを持つに到っています。Rustはその意味論のお蔭で、プログラマが並行性に関して経験する多数の問題を大変にうまく解決することができます。他の言語では実行時エラーになってしまう、並行性に関する多くのエラーが、Rustではコンパイル時エラーになるのです。

Rustの並行性プリミティヴは タスク と呼ばれます。タスクは軽量で、タスク間のコミュニケーションにはメッセージ・パッシングを用いるので、安全でない様態でメモリを共有するということがありません。

spawn(proc() {
    println!("Hello from a task!");
});

spawn函数は、プロックを引数に取り、そのプロックを新たなタスク中で実行します。プロックはその環境の全体に対して所有権を獲得し、そのプロック内で使われるどの変数も、その後には使用することができなくなります:

let mut x = vec![1i, 2i, 3i];

spawn(proc() {
    println!("The value of x[0] is: {}", x[0]);
});

println!("The value of x[0] is: {}", x[0]); // error: use of moved value: `x`

xはプロックによって所有されているので、それ以降はもう使用することができません。多くの他の言語ではこうした使用が許されているのですが、そうすると安全でなくなってしまうのです。Rustの借用検査器はこのエラーを捕捉します。

タスクにできることがこれらの値を捕捉することだけだとしたら、たいして役に立つものではありません。幸いなことに、タスク同士は チャネル を通じてお互いにコミュニケートすることができます。チャネルの使い方はこうなります:

let (tx, rx) = channel();

spawn(proc() {
    tx.send("Hello from a task!".to_string());
});

let message = rx.recv();
println!("{}", message);

channel()函数は、チャネルの両端末をReceiver<T>Sender<T>として返します。Sender<T>側に対しては.send()メソッドを用いることができ、Receiver<T>側ではrecv()メソッドでメッセージを受け取ることができます。このメソッドはメッセージを受け取るまではブロックします。また、.try_recv()という同様のメソッドがありますが、こちらはResult<T, TryRecvError>を返し、ブロックしません。

もしメッセージを受け取るだけでなくタスクに送りたければ、チャネルを2つ作成してください!

let (tx1, rx1) = channel();
let (tx2, rx2) = channel();

spawn(proc() {
    tx1.send("Hello from a task!".to_string());
    let message = rx2.recv();
    println!("{}", message);
});

let message = rx1.recv();
println!("{}", message);

tx2.send("Goodbye from main!".to_string());

このプロックには送信端末と受信端末がひとつづつあり、主タスクにも同様にひとつづつあります。さて、これでお互いに好きなように会話をやりとりすることができます。

SenderReceiverが総称型であることに注意してください。チャネルを通じてどんな情報でもやりとりをすることができるのですが、端末は強く型付けされています。ですから、文字列を渡した後で整数を渡そうとするとRustに文句を言われてしまいます。

24.1. Future

これらの基本的なプリミティヴを用いて、多くの様々な並行性パターンを発展させることができます。Rustはこの種のものを幾つか標準ライブラリに同梱しています。たとえば、バックグラウンドである値を計算しようという場合にはFutureが役に立ちます:

use std::sync::Future;

let mut delayed_value = Future::spawn(proc() {
    // 単なる例示なので返すものはなんでも構わない

    12345i
});
println!("value = {}", delayed_value.get());

Future::spawnの呼び出しはspawn()と同様で、プロックを引数に取ります。しかし今回は、チャネルのゴタゴタは必要ありません。単にプロックに値を返させるので構いません。

Future::spawnが返す値はletで束縛できますが、これは変更可能なものでなくてはなりません。というのも、値を計算し終わったならばその値の複製を保存しようとするわけですが、その時にこの束縛が変更不能だと、更新ができないからです。

プロックはバックグラウンドで計算を続け、我々の側ではその計算の最終的な値が必要になった時にそれに対してget()を呼び出します。get()は結果が出るまでブロックしますが、もしバックグラウンドでの計算が終われば、即座にその値を取得できます。

24.2. 成功と失敗

タスクは常に成功するとは限りません。失敗することもありえます。タスクを失敗させようという時には、fail!マクロを呼び出して、メッセージを渡すことができます:

spawn(proc() {
    fail!("Nope.");
});

タスクが失敗した場合、それを回復することはできません。しかし、他のタスクに対して失敗を通知することはできます。そのためにはtask::tryを使います:

use std::task;
use std::rand;

let result = task::try(proc() {
    if rand::random() {
        println!("OK");
    } else {
        fail!("oops!");
    }
});

このタスクはランダムに成功したり失敗したりします。task::tryResult型の値を返しますから、ほかの種類の失敗する可能性のある計算の場合と同様に、応答を処理すればいいのです。

25. マクロ

Rustの最も先進的な機能のひとつが、そのマクロシステムです。函数は値と操作に対する抽象化を提供していますが、マクロは構文に対する抽象化を提供するものです。目下のところRustができないようななにかをできる能力を、Rustに持たせたいとは思いませんか? 自分でRustの能力を拡張するためにマクロを書くことができます。

みなさんは既に、あるマクロをひとつ広範に使用していますよね。つまり、println!です。Rustのマクロを呼び出すときには、感嘆符(!)を使用する必要があります。こうなっているのには2つの理由があります。第1には、マクロを使っている時にそのことをハッキリとさせておくためです。第2には、マクロは融通無碍な構文を許容してしまうので、どこからマクロが始まってどこで終わるのかをRustが知ることができなければならないからです。!(...)という構文がその助けになるのです。

println!についてもう少しお話ししておきましょう。println!を函数として実行することもできたのですが、もしそうしていたら、マクロを用いるよりも劣ったものになっていたことでしょう。なぜかって? ふむ、あるコードからより多くのコードを生成するような、そういうコードを書くことがマクロのお蔭でできるようになります。ですから、次のようにprintln!を呼び出したとき:

let x = 5i;
println!("x is: {}", x);

println!マクロは次のような幾つかのことをしています:

  1. 文字列をパーズして{}がないかどうか探索する。
  2. {}の数が他の引数の数と合っているかどうかチェックする。
  3. このことを考慮しつつ、Rustコードを一揃い生成する。

これは、Rustは全ての型を考慮に入れてコードを生成するのでコンパイル時に型検査が行えるということを意味します。もしprintln!が函数だったらならば、それでもなお型検査を行うことはできるものの、それが行われるのがコンパイル時ではなく実行時になってしまっていたことでしょう。

rustcに特別なフラグを渡してこのことを確かめてみることができます。print.rsというファイルを作成し、次のようなコードを書き込んでください:

fn main() {
    let x = 5i;
    println!("x is: {}", x);
}

このコードはrustc print.rs --pretty=expandedとしてマクロを展開することができます。そうすると、その結果は次のように巨大なものになるでしょう:

#![feature(phase)]
#![no_std]
#![feature(globs)]
#[phase(plugin, link)]
extern crate "std" as std;
extern crate "native" as rt;
#[prelude_import]
use std::prelude::*;
fn main() {
    let x = 5i;
    match (&x,) {
        (__arg0,) => {
            #[inline]
            #[allow(dead_code)]
            static __STATIC_FMTSTR: [&'static str, ..1u] = ["x is: "];
            let __args_vec =
                &[::std::fmt::argument(::std::fmt::secret_show, __arg0)];
            let __args =
                unsafe {
                    ::std::fmt::Arguments::new(__STATIC_FMTSTR, __args_vec)
                };
            ::std::io::stdio::println_args(&__args)
        }
    };
}

ヒュ~! でも、そこまでひどくはないですね。依然としてlet x = 5iとしていることは見て取れます。しかし、その後がちょっとモサモサしています。新たに束縛が3つセットされています。静的なフォーマット文字列、引数のベクター、そして引数、です。それから、println_args函数が、この生成された引数で呼び出されています。

Rustが実際にコンパイルするのはこのコードです。ここにある追加の情報が全て見て取ることができますね。ここで提供されているような型安全性とオプションを、コンパイル時に、このコードをわざわざ全部自分で書かなくても、手に入れることができるのです。これが、マクロが強力な理由です。もしマクロがなければ、きちんと型検査されたprintlnを手に入れるためには、これを全部手で書かなければいけません。

マクロについて更に知りたい場合にはマクロ・ガイドを参照してください。マクロは極めて先進的で、現時点ではなお些か実験的な機能です。しかし、呼び出す分には普通の函数と見かけは同じですから、マクロを深く理解しておく必要はありません。もし自作のマクロが書きたければ、マクロ・ガイドが助けになります。

26. unsafe

最後になりますが、あともうひとつだけ知っておかなければならないRustの概念があります。それがunsafeです。Rustの安全機構がうまく動作しない2つの状況があります。第1は、Cのコードとのインターフェースを行うときです。第2は、ある特定種類の抽象を構築しようというときです。

RustにはFFI(これについてはFFIガイドで読むことができます)のサポートがありますが、Cのコードが安全であることをRustが保証することはできません。ですから、Rustはそうした函数をunsafeというキーワードで標識します。これで、その函数が適切に振る舞わないかもしれないということを表示するわけです。

第2に、なんらかの種類の共有メモリなデータ構造を作成しようという場合です。メモリは単一の所有者によって所有されなければならないのですから、Rustはそのようなものを許容しません。しかしながら、mutexなどを使う場合のようにその共有メモリに対して安全にアクセスしようとしているならば、 あなたは それが安全であることを知っていますが、Rustにはそれがわかりません。unsafeブロックを使えば、あなたを信頼してくれるようにコンパイラに伝えることができます。この場合には、mutexの 内部 実装は安全でないと考えられますが、我々が提供する 外部 インターフェースの方は安全であることになります。この方法によって、コンパイラがダブルチェックしてくれないような機能を実装することを可能にしつつ、それを通常のRustコードの中で効果的に使うことができるようになります。

でも脱出ハッチはシステム全体の安全性を掘り崩しちゃうんじゃないの? ふむ、もしRustコードがセグメンテーション違反を起こすとしたら、それは 間違いなく 安全でないコードのどこかのせいになるわけです。そして、それがどこかを注釈しておくことで、捜索対象範囲が有意に小さくなります

ここでは例を挙げてお話することすらしていませんが、それは、自分が何をやっているかを正確に理解していない限りは安全でないコードを書くべきでない、ということをみなさんに強調しておきたいからです。Rust開発者の圧倒的大多数は、FFIを利用するときに限ってそれに触れることになるはずです。先進的なライブラリの作者であれば、安全でないコードを、ある種の抽象を構築するために使っても宜しいでしょう。

27. おわりに

ここまででかなりの範囲を踏破しました。このガイドの全てをマスターした暁には、Rustによる開発の基本をしっかりと理解できたことになります。もっと多くのことがまるまる広がっています。私達が踏破したのはその表面に過ぎません。もっと深く掘り下げることのできるトピックがたくさんありますし、その多くについてはそれに特化したガイドを作成してあります。更なる学習のためには、全ドキュメントのインデックスを調べて見てください。

Happy hacking!
 


原文の著作権ライセンスは下記のとおりです。
また、この日本語訳はApache License, version 2.0に拠ります。

Copyright © 2011-2014 The Rust Project Developers.
Licensed under the Apache License, Version 2.0 or the MIT license,
at your option.

This file may not be copied, modified, or distributed except
according to those terms.