From 48f45bff58194b57d10c213ae7c6df0e8ab6a4ad Mon Sep 17 00:00:00 2001 From: "bn-e (CircleCI)" Date: Sun, 11 Oct 2020 03:11:30 +0000 Subject: [PATCH] ci: generate pages at b3e47dc7 [ci skip] --- docs/appendix-06-translation.html | 26 ++++++++++++++++++++++++-- docs/print.html | 26 ++++++++++++++++++++++++-- docs/searchindex.js | 2 +- docs/searchindex.json | 2 +- 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/docs/appendix-06-translation.html b/docs/appendix-06-translation.html index 77f8c6935..5d0f2ec0a 100644 --- a/docs/appendix-06-translation.html +++ b/docs/appendix-06-translation.html @@ -161,8 +161,30 @@

付 For resources in languages other than English. Most are still in progress; see [the Translations label][label] to help or let us know about a new translation! --> -

英語以外の言語のリソースです。ほとんどは翻訳中です; Translationsラベルを確認して、 -新しい翻訳の手助けや開始したことをお知らせください!

+

英語以外の言語の資料についてです。ほとんどはまだ翻訳中です。 +手助けいただける際や、新しい翻訳について教えていただける際は、 +Translationsラベルを確認してください!

+ diff --git a/docs/print.html b/docs/print.html index 2ed75facd..f801af38e 100644 --- a/docs/print.html +++ b/docs/print.html @@ -43590,8 +43590,30 @@

付 For resources in languages other than English. Most are still in progress; see [the Translations label][label] to help or let us know about a new translation! --> -

英語以外の言語のリソースです。ほとんどは翻訳中です; Translationsラベルを確認して、 -新しい翻訳の手助けや開始したことをお知らせください!

+

英語以外の言語の資料についてです。ほとんどはまだ翻訳中です。 +手助けいただける際や、新しい翻訳について教えていただける際は、 +Translationsラベルを確認してください!

+ diff --git a/docs/searchindex.js b/docs/searchindex.js index 2f6b81d60..4c1e573e8 100644 --- a/docs/searchindex.js +++ b/docs/searchindex.js @@ -1 +1 @@ -Object.assign(window.search, {"doc_urls":["title-page.html#the-rust-programming-language-日本語版","foreword.html#まえがき","ch00-00-introduction.html#導入","ch00-00-introduction.html#rustは誰のためのものなの","ch00-00-introduction.html#開発者チーム","ch00-00-introduction.html#学生","ch00-00-introduction.html#企業","ch00-00-introduction.html#オープンソース開発者","ch00-00-introduction.html#スピードと安定性に価値を見出す方","ch00-00-introduction.html#この本は誰のためのものなの","ch00-00-introduction.html#この本の使い方","ch00-00-introduction.html#ソースコード","ch01-00-getting-started.html#事始め","ch01-01-installation.html#インストール","ch01-01-installation.html#コマンドライン表記","ch01-01-installation.html#linuxとmacosにrustupをインストールする","ch01-01-installation.html#windowsでrustupをインストールする","ch01-01-installation.html#更新及びアンインストール","ch01-01-installation.html#トラブルシューティング","ch01-01-installation.html#ローカルのドキュメンテーション","ch01-02-hello-world.html#hello-world","ch01-02-hello-world.html#プロジェクトのディレクトリを作成する","ch01-02-hello-world.html#rustプログラムを書いて走らせる","ch01-02-hello-world.html#rustプログラムの解剖","ch01-02-hello-world.html#コンパイルと実行は個別のステップ","ch01-03-hello-cargo.html#hello-cargo","ch01-03-hello-cargo.html#cargoでプロジェクトを作成する","ch01-03-hello-cargo.html#cargoプロジェクトをビルドし実行する","ch01-03-hello-cargo.html#リリースビルドを行う","ch01-03-hello-cargo.html#習慣としてのcargo","ch01-03-hello-cargo.html#まとめ","ch02-00-guessing-game-tutorial.html#数当てゲームをプログラムする","ch02-00-guessing-game-tutorial.html#新規プロジェクトの立ち上げ","ch02-00-guessing-game-tutorial.html#予想を処理する","ch02-00-guessing-game-tutorial.html#値を変数に保持する","ch02-00-guessing-game-tutorial.html#result型で失敗の可能性を扱う","ch02-00-guessing-game-tutorial.html#printlnマクロのプレースホルダーで値を出力する","ch02-00-guessing-game-tutorial.html#最初の部分をテストする","ch02-00-guessing-game-tutorial.html#秘密の数字を生成する","ch02-00-guessing-game-tutorial.html#クレートを使用して機能を追加する","ch02-00-guessing-game-tutorial.html#乱数を生成する","ch02-00-guessing-game-tutorial.html#予想と秘密の数字を比較する","ch02-00-guessing-game-tutorial.html#ループで複数回の予想を可能にする","ch02-00-guessing-game-tutorial.html#正しい予想をした後に終了する","ch02-00-guessing-game-tutorial.html#不正な入力を処理する","ch02-00-guessing-game-tutorial.html#まとめ","ch03-00-common-programming-concepts.html#一般的なプログラミングの概念","ch03-00-common-programming-concepts.html#キーワード","ch03-01-variables-and-mutability.html#変数と可変性","ch03-01-variables-and-mutability.html#変数と定数constantsの違い","ch03-01-variables-and-mutability.html#シャドーイング","ch03-02-data-types.html#データ型","ch03-02-data-types.html#スカラー型","ch03-02-data-types.html#複合型","ch03-03-how-functions-work.html#関数","ch03-03-how-functions-work.html#関数の引数","ch03-03-how-functions-work.html#関数本体は文と式を含む","ch03-03-how-functions-work.html#戻り値のある関数","ch03-04-comments.html#コメント","ch03-05-control-flow.html#フロー制御","ch03-05-control-flow.html#if式","ch03-05-control-flow.html#ループでの繰り返し","ch03-05-control-flow.html#まとめ","ch04-00-understanding-ownership.html#所有権を理解する","ch04-01-what-is-ownership.html#所有権とは","ch04-01-what-is-ownership.html#スタックとヒープ","ch04-01-what-is-ownership.html#所有権規則","ch04-01-what-is-ownership.html#変数スコープ","ch04-01-what-is-ownership.html#string型","ch04-01-what-is-ownership.html#メモリと確保","ch04-01-what-is-ownership.html#所有権と関数","ch04-01-what-is-ownership.html#戻り値とスコープ","ch04-02-references-and-borrowing.html#参照と借用","ch04-02-references-and-borrowing.html#可変な参照","ch04-02-references-and-borrowing.html#宙に浮いた参照","ch04-02-references-and-borrowing.html#参照の規則","ch04-03-slices.html#スライス型","ch04-03-slices.html#文字列スライス","ch04-03-slices.html#他のスライス","ch04-03-slices.html#まとめ","ch05-00-structs.html#構造体を使用して関係のあるデータを構造化する","ch05-01-defining-structs.html#構造体を定義しインスタンス化する","ch05-01-defining-structs.html#フィールドと変数が同名の時にフィールド初期化省略記法を使う","ch05-01-defining-structs.html#構造体更新記法で他のインスタンスからインスタンスを生成する","ch05-01-defining-structs.html#異なる型を生成する名前付きフィールドのないタプル構造体を使用する","ch05-01-defining-structs.html#フィールドのないユニット様よう構造体","ch05-01-defining-structs.html#構造体データの所有権","ch05-02-example-structs.html#構造体を使ったプログラム例","ch05-02-example-structs.html#タプルでリファクタリングする","ch05-02-example-structs.html#タプルでリファクタリングする","ch05-02-example-structs.html#構造体でリファクタリングする-より意味付けする","ch05-02-example-structs.html#トレイトの導出で有用な機能を追加する","ch05-03-method-syntax.html#メソッド記法","ch05-03-method-syntax.html#メソッドを定義する","ch05-03-method-syntax.html#-演算子はどこに行ったの","ch05-03-method-syntax.html#より引数の多いメソッド","ch05-03-method-syntax.html#関連関数","ch05-03-method-syntax.html#複数のimplブロック","ch05-03-method-syntax.html#まとめ","ch06-00-enums.html#enumとパターンマッチング","ch06-01-defining-an-enum.html#enumを定義する","ch06-01-defining-an-enum.html#enumの値","ch06-01-defining-an-enum.html#option-enumとnull値に勝る利点","ch06-02-match.html#matchフロー制御演算子","ch06-02-match.html#値に束縛されるパターン","ch06-02-match.html#optiontとのマッチ","ch06-02-match.html#マッチは包括的","ch06-02-match.html#_というプレースホルダー","ch06-03-if-let.html#if-letで簡潔なフロー制御","ch06-03-if-let.html#まとめ","ch07-00-managing-growing-projects-with-packages-crates-and-modules.html#肥大化していくプロジェクトをパッケージクレートモジュールを利用して管理する","ch07-01-packages-and-crates.html#パッケージとクレート","ch07-02-defining-modules-to-control-scope-and-privacy.html#モジュールを定義してスコープとプライバシーを制御する","ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html#モジュールツリーの要素を示すためのパス","ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html#パスをpubキーワードで公開する","ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html#相対パスをsuperで始める","ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html#構造体とenumを公開する","ch07-04-bringing-paths-into-scope-with-the-use-keyword.html#useキーワードでパスをスコープに持ち込む","ch07-04-bringing-paths-into-scope-with-the-use-keyword.html#慣例に従ったuseパスを作る","ch07-04-bringing-paths-into-scope-with-the-use-keyword.html#新しい名前をasキーワードで与える","ch07-04-bringing-paths-into-scope-with-the-use-keyword.html#pub-useを使って名前を再公開する","ch07-04-bringing-paths-into-scope-with-the-use-keyword.html#外部のパッケージを使う","ch07-04-bringing-paths-into-scope-with-the-use-keyword.html#巨大なuseのリストをネストしたパスを使って整理する","ch07-04-bringing-paths-into-scope-with-the-use-keyword.html#glob演算子","ch07-05-separating-modules-into-different-files.html#モジュールを複数のファイルに分割する","ch07-05-separating-modules-into-different-files.html#まとめ","ch08-00-common-collections.html#一般的なコレクション","ch08-01-vectors.html#ベクタで一連の値を保持する","ch08-01-vectors.html#新しいベクタを生成する","ch08-01-vectors.html#ベクタを更新する","ch08-01-vectors.html#ベクタをドロップすれば要素もドロップする","ch08-01-vectors.html#ベクタの要素を読む","ch08-01-vectors.html#ベクタの値を走査する","ch08-01-vectors.html#enumを使って複数の型を保持する","ch08-02-strings.html#文字列でutf-8でエンコードされたテキストを保持する","ch08-02-strings.html#文字列とは","ch08-02-strings.html#新規文字列を生成する","ch08-02-strings.html#文字列を更新する","ch08-02-strings.html#文字列に添え字アクセスする","ch08-02-strings.html#文字列をスライスする","ch08-02-strings.html#文字列を走査するメソッド群","ch08-02-strings.html#文字列はそう単純じゃない","ch08-03-hash-maps.html#キーとそれに紐づいた値をハッシュマップに格納する","ch08-03-hash-maps.html#新規ハッシュマップを生成する","ch08-03-hash-maps.html#ハッシュマップと所有権","ch08-03-hash-maps.html#ハッシュマップの値にアクセスする","ch08-03-hash-maps.html#ハッシュマップを更新する","ch08-03-hash-maps.html#ハッシュ関数","ch08-03-hash-maps.html#まとめ","ch09-00-error-handling.html#エラー処理","ch09-01-unrecoverable-errors-with-panic.html#panicで回復不能なエラー","ch09-01-unrecoverable-errors-with-panic.html#パニックに対してスタックを巻き戻すか異常終了するか","ch09-01-unrecoverable-errors-with-panic.html#panicバックトレースを使用する","ch09-02-recoverable-errors-with-result.html#resultで回復可能なエラー","ch09-02-recoverable-errors-with-result.html#色々なエラーにマッチする","ch09-02-recoverable-errors-with-result.html#エラー時にパニックするショートカット-unwrapとexpect","ch09-02-recoverable-errors-with-result.html#エラーを委譲する","ch09-03-to-panic-or-not-to-panic.html#panicすべきかするまいか","ch09-03-to-panic-or-not-to-panic.html#例プロトタイプコードテスト","ch09-03-to-panic-or-not-to-panic.html#コンパイラよりもプログラマがより情報を持っている場合","ch09-03-to-panic-or-not-to-panic.html#エラー処理のガイドライン","ch09-03-to-panic-or-not-to-panic.html#検証のために独自の型を作る","ch09-03-to-panic-or-not-to-panic.html#まとめ","ch10-00-generics.html#ジェネリック型トレイトライフタイム","ch10-00-generics.html#関数を抽出することで重複を取り除く","ch10-01-syntax.html#ジェネリックなデータ型","ch10-01-syntax.html#関数定義では","ch10-01-syntax.html#構造体定義では","ch10-01-syntax.html#enum定義では","ch10-01-syntax.html#メソッド定義では","ch10-01-syntax.html#ジェネリクスを使用したコードのパフォーマンス","ch10-02-traits.html#トレイト-共通の振る舞いを定義する","ch10-02-traits.html#トレイトを定義する","ch10-02-traits.html#トレイトを型に実装する","ch10-02-traits.html#デフォルト実装","ch10-02-traits.html#引数としてのトレイト","ch10-02-traits.html#トレイトを実装している型を返す","ch10-02-traits.html#トレイト境界でlargest関数を修正する","ch10-02-traits.html#トレイト境界を使用してメソッド実装を条件分けする","ch10-03-lifetime-syntax.html#ライフタイムで参照を検証する","ch10-03-lifetime-syntax.html#ライフタイムでダングリング参照を回避する","ch10-03-lifetime-syntax.html#借用精査機","ch10-03-lifetime-syntax.html#関数のジェネリックなライフタイム","ch10-03-lifetime-syntax.html#ライフタイム注釈記法","ch10-03-lifetime-syntax.html#関数シグニチャにおけるライフタイム注釈","ch10-03-lifetime-syntax.html#ライフタイムの観点で思考する","ch10-03-lifetime-syntax.html#構造体定義のライフタイム注釈","ch10-03-lifetime-syntax.html#ライフタイム省略","ch10-03-lifetime-syntax.html#メソッド定義におけるライフタイム注釈","ch10-03-lifetime-syntax.html#静的ライフタイム","ch10-03-lifetime-syntax.html#ジェネリックな型引数トレイト境界ライフタイムを一度に","ch10-03-lifetime-syntax.html#まとめ","ch11-00-testing.html#自動テストを書く","ch11-01-writing-tests.html#テストの記述法","ch11-01-writing-tests.html#テスト関数の構成","ch11-01-writing-tests.html#assertマクロで結果を確認する","ch11-01-writing-tests.html#assert_eqとassert_neマクロで等値性をテストする","ch11-01-writing-tests.html#カスタムの失敗メッセージを追加する","ch11-01-writing-tests.html#should_panicでパニックを確認する","ch11-01-writing-tests.html#resultt-eをテストで使う","ch11-02-running-tests.html#テストの実行のされ方を制御する","ch11-02-running-tests.html#テストを並行または連続して実行する","ch11-02-running-tests.html#関数の出力を表示する","ch11-02-running-tests.html#名前でテストの一部を実行する","ch11-02-running-tests.html#特に要望のない限りテストを無視する","ch11-03-test-organization.html#テストの体系化","ch11-03-test-organization.html#単体テスト","ch11-03-test-organization.html#結合テスト","ch11-03-test-organization.html#まとめ","ch12-00-an-io-project.html#入出力プロジェクト-コマンドラインプログラムを構築する","ch12-01-accepting-command-line-arguments.html#コマンドライン引数を受け付ける","ch12-01-accepting-command-line-arguments.html#引数の値を読み取る","ch12-01-accepting-command-line-arguments.html#args関数と不正なユニコード","ch12-01-accepting-command-line-arguments.html#引数の値を変数に保存する","ch12-02-reading-a-file.html#ファイルを読み込む","ch12-03-improving-error-handling-and-modularity.html#リファクタリングしてモジュール性とエラー処理を向上させる","ch12-03-improving-error-handling-and-modularity.html#バイナリプロジェクトの責任の分離","ch12-03-improving-error-handling-and-modularity.html#cloneを使用する代償","ch12-03-improving-error-handling-and-modularity.html#エラー処理を修正する","ch12-03-improving-error-handling-and-modularity.html#mainからロジックを抽出する","ch12-03-improving-error-handling-and-modularity.html#コードをライブラリクレートに分割する","ch12-04-testing-the-librarys-functionality.html#テスト駆動開発でライブラリの機能を開発する","ch12-04-testing-the-librarys-functionality.html#失敗するテストを記述する","ch12-04-testing-the-librarys-functionality.html#テストを通過させるコードを書く","ch12-05-working-with-environment-variables.html#環境変数を取り扱う","ch12-05-working-with-environment-variables.html#大文字小文字を区別しないsearch関数用に失敗するテストを書く","ch12-05-working-with-environment-variables.html#search_case_insensitive関数を実装する","ch12-06-writing-to-stderr-instead-of-stdout.html#標準出力ではなく標準エラーにエラーメッセージを書き込む","ch12-06-writing-to-stderr-instead-of-stdout.html#エラーが書き込まれる場所を確認する","ch12-06-writing-to-stderr-instead-of-stdout.html#エラーを標準エラーに出力する","ch12-06-writing-to-stderr-instead-of-stdout.html#まとめ","ch13-00-functional-features.html#関数型言語の機能-イテレータとクロージャ","ch13-01-closures.html#クロージャ-環境をキャプチャできる匿名関数","ch13-01-closures.html#クロージャで動作の抽象化を行う","ch13-01-closures.html#クロージャの型推論と注釈","ch13-01-closures.html#ジェネリック引数とfnトレイトを使用してクロージャを保存する","ch13-01-closures.html#cacher実装の限界","ch13-01-closures.html#クロージャで環境をキャプチャする","ch13-02-iterators.html#一連の要素をイテレータで処理する","ch13-02-iterators.html#iteratorトレイトとnextメソッド","ch13-02-iterators.html#イテレータを消費するメソッド","ch13-02-iterators.html#他のイテレータを生成するメソッド","ch13-02-iterators.html#環境をキャプチャするクロージャを使用する","ch13-02-iterators.html#iteratorトレイトで独自のイテレータを作成する","ch13-03-improving-our-io-project.html#入出力プロジェクトを改善する","ch13-03-improving-our-io-project.html#イテレータを使用してcloneを取り除く","ch13-03-improving-our-io-project.html#イテレータアダプタでコードをより明確にする","ch13-04-performance.html#パフォーマンス比較-ループvsイテレータ","ch13-04-performance.html#まとめ","ch14-00-more-about-cargo.html#cargoとcratesioについてより詳しく","ch14-01-release-profiles.html#リリースプロファイルでビルドをカスタマイズする","ch14-02-publishing-to-crates-io.html#cratesioにクレートを公開する","ch14-02-publishing-to-crates-io.html#役に立つドキュメンテーションコメントを行う","ch14-02-publishing-to-crates-io.html#pub-useで便利な公開apiをエクスポートする","ch14-02-publishing-to-crates-io.html#cratesioのアカウントをセットアップする","ch14-02-publishing-to-crates-io.html#新しいクレートにメタデータを追加する","ch14-02-publishing-to-crates-io.html#cratesioに公開する","ch14-02-publishing-to-crates-io.html#既存のクレートの新バージョンを公開する","ch14-02-publishing-to-crates-io.html#cargo-yankでcratesioからバージョンを削除する","ch14-03-cargo-workspaces.html#cargoのワークスペース","ch14-03-cargo-workspaces.html#ワークスペースを生成する","ch14-03-cargo-workspaces.html#ワークスペース内に2番目のクレートを作成する","ch14-04-installing-binaries.html#cargo-installでcratesioからバイナリをインストールする","ch14-05-extending-cargo.html#独自のコマンドでcargoを拡張する","ch14-05-extending-cargo.html#まとめ","ch15-00-smart-pointers.html#スマートポインタ","ch15-01-box.html#ヒープのデータを指すboxtを使用する","ch15-01-box.html#boxtを使ってヒープにデータを格納する","ch15-01-box.html#ボックスで再帰的な型を可能にする","ch15-02-deref.html#derefトレイトでスマートポインタを普通の参照のように扱う","ch15-02-deref.html#参照外し演算子で値までポインタを追いかける","ch15-02-deref.html#boxtを参照のように使う","ch15-02-deref.html#独自のスマートポインタを定義する","ch15-02-deref.html#derefトレイトを実装して型を参照のように扱う","ch15-02-deref.html#関数やメソッドで暗黙的な参照外し型強制","ch15-02-deref.html#参照外し型強制が可変性と相互作用する方法","ch15-03-drop.html#dropトレイトで片付け時にコードを走らせる","ch15-03-drop.html#stdmemdropで早期に値をドロップする","ch15-04-rc.html#rctは参照カウント方式のスマートポインタ","ch15-04-rc.html#rctでデータを共有する","ch15-04-rc.html#rctをクローンすると参照カウントが増える","ch15-05-interior-mutability.html#refcelltと内部可変性パターン","ch15-05-interior-mutability.html#refcelltで実行時に借用規則を強制する","ch15-05-interior-mutability.html#内部可変性-不変値への可変借用","ch15-05-interior-mutability.html#rctとrefcelltを組み合わせることで可変なデータに複数の所有者を持たせる","ch15-06-reference-cycles.html#循環参照はメモリをリークすることもある","ch15-06-reference-cycles.html#循環参照させる","ch15-06-reference-cycles.html#循環参照を回避する-rctをweaktに変換する","ch15-06-reference-cycles.html#まとめ","ch16-00-concurrency.html#恐れるな並行性","ch16-01-threads.html#スレッドを使用してコードを同時に走らせる","ch16-01-threads.html#spawnで新規スレッドを生成する","ch16-01-threads.html#joinハンドルで全スレッドの終了を待つ","ch16-01-threads.html#スレッドでmoveクロージャを使用する","ch16-02-message-passing.html#メッセージ受け渡しを使ってスレッド間でデータを転送する","ch16-02-message-passing.html#チャンネルと所有権の転送","ch16-02-message-passing.html#複数の値を送信し受信側が待機するのを確かめる","ch16-02-message-passing.html#転送機をクローンして複数の生成器を作成する","ch16-03-shared-state.html#状態共有並行性","ch16-03-shared-state.html#ミューテックスを使用して一度に1つのスレッドからデータにアクセスすることを許可する","ch16-03-shared-state.html#refcelltrctとmutextarctの類似性","ch16-04-extensible-concurrency-sync-and-send.html#syncとsendトレイトで拡張可能な並行性","ch16-04-extensible-concurrency-sync-and-send.html#sendでスレッド間の所有権の転送を許可する","ch16-04-extensible-concurrency-sync-and-send.html#syncで複数のスレッドからのアクセスを許可する","ch16-04-extensible-concurrency-sync-and-send.html#sendとsyncを手動で実装するのは非安全である","ch16-04-extensible-concurrency-sync-and-send.html#まとめ","ch17-00-oop.html#rustのオブジェクト指向プログラミング機能","ch17-01-what-is-oo.html#オブジェクト指向言語の特徴","ch17-01-what-is-oo.html#オブジェクトはデータと振る舞いを含む","ch17-01-what-is-oo.html#カプセル化は実装詳細を隠蔽する","ch17-01-what-is-oo.html#型システムおよびコード共有としての継承","ch17-01-what-is-oo.html#多相性","ch17-02-trait-objects.html#トレイトオブジェクトで異なる型の値を許容する","ch17-02-trait-objects.html#一般的な振る舞いにトレイトを定義する","ch17-02-trait-objects.html#トレイトを実装する","ch17-02-trait-objects.html#トレイトオブジェクトはダイナミックディスパッチを行う","ch17-02-trait-objects.html#トレイトオブジェクトにはオブジェクト安全性が必要","ch17-03-oo-design-patterns.html#オブジェクト指向デザインパターンを実装する","ch17-03-oo-design-patterns.html#postを定義し草稿状態で新しいインスタンスを生成する","ch17-03-oo-design-patterns.html#記事の内容のテキストを格納する","ch17-03-oo-design-patterns.html#草稿の記事の内容は空であることを保証する","ch17-03-oo-design-patterns.html#記事の査読を要求すると状態が変化する","ch17-03-oo-design-patterns.html#contentの振る舞いを変化させるapproveメソッドを追加する","ch17-03-oo-design-patterns.html#ステートパターンの代償","ch17-03-oo-design-patterns.html#まとめ","ch18-00-patterns.html#パターンとマッチング","ch18-01-all-the-places-for-patterns.html#パターンが使用されることのある箇所全部","ch18-01-all-the-places-for-patterns.html#matchアーム","ch18-01-all-the-places-for-patterns.html#条件分岐if-let式","ch18-01-all-the-places-for-patterns.html#while-let条件分岐ループ","ch18-01-all-the-places-for-patterns.html#forループ","ch18-01-all-the-places-for-patterns.html#let文","ch18-01-all-the-places-for-patterns.html#関数の引数","ch18-02-refutability.html#論駁可能性-パターンが合致しないかどうか","ch18-03-pattern-syntax.html#パターン記法","ch18-03-pattern-syntax.html#リテラルにマッチする","ch18-03-pattern-syntax.html#名前付き変数にマッチする","ch18-03-pattern-syntax.html#複数のパターン","ch18-03-pattern-syntax.html#で値の範囲に合致させる","ch18-03-pattern-syntax.html#分配して値を分解する","ch18-03-pattern-syntax.html#パターンの値を無視する","ch18-03-pattern-syntax.html#refとref-mutでパターンに参照を生成する","ch18-03-pattern-syntax.html#マッチガードで追加の条件式","ch18-03-pattern-syntax.html#束縛","ch18-03-pattern-syntax.html#まとめ","ch19-00-advanced-features.html#高度な機能","ch19-01-unsafe-rust.html#unsafe-rust","ch19-01-unsafe-rust.html#unsafeの強大な力superpower","ch19-01-unsafe-rust.html#生ポインタを参照外しする","ch19-01-unsafe-rust.html#unsafeな関数やメソッドを呼ぶ","ch19-01-unsafe-rust.html#可変で静的な変数にアクセスしたり変更する","ch19-01-unsafe-rust.html#unsafeなトレイトを実装する","ch19-01-unsafe-rust.html#いつunsafeコードを使用するべきか","ch19-03-advanced-traits.html#高度なトレイト","ch19-03-advanced-traits.html#関連型でトレイト定義においてプレースホルダーの型を指定する","ch19-03-advanced-traits.html#デフォルトのジェネリック型引数と演算子オーバーロード","ch19-03-advanced-traits.html#明確化のためのフルパス記法-同じ名前のメソッドを呼ぶ","ch19-03-advanced-traits.html#スーパートレイトを使用して別のトレイト内であるトレイトの機能を必要とする","ch19-03-advanced-traits.html#ニュータイプパターンを使用して外部の型に外部のトレイトを実装する","ch19-04-advanced-types.html#高度な型","ch19-04-advanced-types.html#型安全性と抽象化を求めてニュータイプパターンを使用する","ch19-04-advanced-types.html#型エイリアスで型同義語を生成する","ch19-04-advanced-types.html#never型は絶対に返らない","ch19-04-advanced-types.html#動的サイズ付け型とsizedトレイト","ch19-05-advanced-functions-and-closures.html#高度な関数とクロージャ","ch19-05-advanced-functions-and-closures.html#関数ポインタ","ch19-05-advanced-functions-and-closures.html#クロージャを返却する","ch19-05-advanced-functions-and-closures.html#まとめ","ch19-06-macros.html#マクロ","ch19-06-macros.html#マクロと関数の違い","ch19-06-macros.html#一般的なメタプログラミングのためにmacro_rulesで宣言的なマクロ","ch19-06-macros.html#属性からコードを生成する手続き的マクロ","ch19-06-macros.html#カスタムのderive-マクロの書き方","ch19-06-macros.html#属性風マクロ","ch19-06-macros.html#関数風マクロ","ch19-06-macros.html#まとめ","ch20-00-final-project-a-web-server.html#最後のプロジェクト-マルチスレッドのwebサーバを構築する","ch20-01-single-threaded.html#シングルスレッドのwebサーバを構築する","ch20-01-single-threaded.html#tcp接続をリッスンする","ch20-01-single-threaded.html#リクエストを読み取る","ch20-01-single-threaded.html#httpリクエストを詳しく見る","ch20-01-single-threaded.html#レスポンスを記述する","ch20-01-single-threaded.html#本物のhtmlを返す","ch20-01-single-threaded.html#リクエストにバリデーションをかけ選択的にレスポンスを返す","ch20-01-single-threaded.html#リファクタリングの触り","ch20-02-multithreaded.html#シングルスレッドサーバをマルチスレッド化する","ch20-02-multithreaded.html#現在のサーバの実装で遅いリクエストをシミュレーションする","ch20-02-multithreaded.html#スレッドプールでスループットを向上させる","ch20-03-graceful-shutdown-and-cleanup.html#正常なシャットダウンと片付け","ch20-03-graceful-shutdown-and-cleanup.html#threadpoolにdropトレイトを実装する","ch20-03-graceful-shutdown-and-cleanup.html#スレッドに仕事をリッスンするのを止めるよう通知する","ch20-03-graceful-shutdown-and-cleanup.html#まとめ","appendix-00.html#付録","appendix-01-keywords.html#付録a-キーワード","appendix-01-keywords.html#現在使用されているキーワード","appendix-01-keywords.html#将来的な使用のために予約されているキーワード","appendix-01-keywords.html#生識別子","appendix-02-operators.html#付録b-演算子と記号","appendix-02-operators.html#演算子","appendix-02-operators.html#演算子以外のシンボル","appendix-03-derivable-traits.html#付録c-導出可能なトレイト","appendix-03-derivable-traits.html#プログラマ用の出力のdebug","appendix-03-derivable-traits.html#等価比較のためのpartialeqとeq","appendix-03-derivable-traits.html#順序付き比較のためのpartialordとord","appendix-03-derivable-traits.html#値を複製するcloneとcopy","appendix-03-derivable-traits.html#値を固定サイズの値にマップするhash","appendix-03-derivable-traits.html#既定値のためのdefault","appendix-04-useful-development-tools.html#付録d---便利な開発ツール","appendix-04-useful-development-tools.html#rustfmtを使った自動フォーマット","appendix-04-useful-development-tools.html#rustfixでコードを修正する","appendix-04-useful-development-tools.html#clippyでもっとlintを","appendix-04-useful-development-tools.html#rust-language-serverを使ってideと統合する","appendix-05-editions.html#付録e---edition","appendix-06-translation.html#付録f-本の翻訳","appendix-07-nightly-rust.html#付録g-rustの作られ方とnightly-rust","appendix-07-nightly-rust.html#停滞なしの安定性","appendix-07-nightly-rust.html#シュポシュポリリースチャンネルと列車に乗ること","appendix-07-nightly-rust.html#安定しない機能","appendix-07-nightly-rust.html#rustupとrustナイトリの役目","appendix-07-nightly-rust.html#rfcプロセスとチーム"],"index":{"documentStore":{"docInfo":{"0":{"body":47,"breadcrumbs":3,"title":3},"1":{"body":17,"breadcrumbs":0,"title":0},"10":{"body":31,"breadcrumbs":0,"title":0},"100":{"body":15,"breadcrumbs":2,"title":1},"101":{"body":186,"breadcrumbs":2,"title":1},"102":{"body":99,"breadcrumbs":3,"title":2},"103":{"body":69,"breadcrumbs":2,"title":1},"104":{"body":59,"breadcrumbs":1,"title":0},"105":{"body":46,"breadcrumbs":2,"title":1},"106":{"body":27,"breadcrumbs":1,"title":0},"107":{"body":21,"breadcrumbs":2,"title":1},"108":{"body":83,"breadcrumbs":1,"title":0},"109":{"body":6,"breadcrumbs":1,"title":0},"11":{"body":1,"breadcrumbs":0,"title":0},"110":{"body":8,"breadcrumbs":0,"title":0},"111":{"body":51,"breadcrumbs":0,"title":0},"112":{"body":71,"breadcrumbs":0,"title":0},"113":{"body":109,"breadcrumbs":0,"title":0},"114":{"body":130,"breadcrumbs":1,"title":1},"115":{"body":26,"breadcrumbs":1,"title":1},"116":{"body":106,"breadcrumbs":1,"title":1},"117":{"body":66,"breadcrumbs":1,"title":1},"118":{"body":93,"breadcrumbs":1,"title":1},"119":{"body":33,"breadcrumbs":0,"title":0},"12":{"body":5,"breadcrumbs":0,"title":0},"120":{"body":47,"breadcrumbs":2,"title":2},"121":{"body":59,"breadcrumbs":0,"title":0},"122":{"body":139,"breadcrumbs":1,"title":1},"123":{"body":8,"breadcrumbs":1,"title":1},"124":{"body":62,"breadcrumbs":0,"title":0},"125":{"body":3,"breadcrumbs":0,"title":0},"126":{"body":3,"breadcrumbs":0,"title":0},"127":{"body":2,"breadcrumbs":0,"title":0},"128":{"body":22,"breadcrumbs":0,"title":0},"129":{"body":15,"breadcrumbs":0,"title":0},"13":{"body":6,"breadcrumbs":0,"title":0},"130":{"body":11,"breadcrumbs":0,"title":0},"131":{"body":93,"breadcrumbs":0,"title":0},"132":{"body":23,"breadcrumbs":0,"title":0},"133":{"body":26,"breadcrumbs":1,"title":1},"134":{"body":8,"breadcrumbs":2,"title":2},"135":{"body":15,"breadcrumbs":0,"title":0},"136":{"body":73,"breadcrumbs":0,"title":0},"137":{"body":117,"breadcrumbs":0,"title":0},"138":{"body":88,"breadcrumbs":0,"title":0},"139":{"body":22,"breadcrumbs":0,"title":0},"14":{"body":1,"breadcrumbs":0,"title":0},"140":{"body":19,"breadcrumbs":0,"title":0},"141":{"body":4,"breadcrumbs":0,"title":0},"142":{"body":7,"breadcrumbs":0,"title":0},"143":{"body":40,"breadcrumbs":0,"title":0},"144":{"body":29,"breadcrumbs":0,"title":0},"145":{"body":39,"breadcrumbs":0,"title":0},"146":{"body":80,"breadcrumbs":0,"title":0},"147":{"body":7,"breadcrumbs":0,"title":0},"148":{"body":14,"breadcrumbs":0,"title":0},"149":{"body":9,"breadcrumbs":0,"title":0},"15":{"body":21,"breadcrumbs":1,"title":1},"150":{"body":1,"breadcrumbs":1,"title":1},"151":{"body":48,"breadcrumbs":0,"title":0},"152":{"body":156,"breadcrumbs":1,"title":1},"153":{"body":127,"breadcrumbs":1,"title":1},"154":{"body":60,"breadcrumbs":0,"title":0},"155":{"body":78,"breadcrumbs":1,"title":1},"156":{"body":174,"breadcrumbs":0,"title":0},"157":{"body":6,"breadcrumbs":1,"title":1},"158":{"body":4,"breadcrumbs":0,"title":0},"159":{"body":16,"breadcrumbs":0,"title":0},"16":{"body":11,"breadcrumbs":1,"title":1},"160":{"body":13,"breadcrumbs":0,"title":0},"161":{"body":86,"breadcrumbs":0,"title":0},"162":{"body":8,"breadcrumbs":0,"title":0},"163":{"body":7,"breadcrumbs":0,"title":0},"164":{"body":145,"breadcrumbs":0,"title":0},"165":{"body":1,"breadcrumbs":0,"title":0},"166":{"body":142,"breadcrumbs":0,"title":0},"167":{"body":113,"breadcrumbs":0,"title":0},"168":{"body":30,"breadcrumbs":1,"title":1},"169":{"body":120,"breadcrumbs":0,"title":0},"17":{"body":7,"breadcrumbs":0,"title":0},"170":{"body":32,"breadcrumbs":0,"title":0},"171":{"body":1,"breadcrumbs":0,"title":0},"172":{"body":23,"breadcrumbs":0,"title":0},"173":{"body":128,"breadcrumbs":0,"title":0},"174":{"body":218,"breadcrumbs":0,"title":0},"175":{"body":152,"breadcrumbs":0,"title":0},"176":{"body":187,"breadcrumbs":0,"title":0},"177":{"body":252,"breadcrumbs":1,"title":1},"178":{"body":63,"breadcrumbs":0,"title":0},"179":{"body":3,"breadcrumbs":0,"title":0},"18":{"body":21,"breadcrumbs":0,"title":0},"180":{"body":51,"breadcrumbs":0,"title":0},"181":{"body":32,"breadcrumbs":0,"title":0},"182":{"body":80,"breadcrumbs":0,"title":0},"183":{"body":19,"breadcrumbs":0,"title":0},"184":{"body":127,"breadcrumbs":0,"title":0},"185":{"body":71,"breadcrumbs":0,"title":0},"186":{"body":31,"breadcrumbs":0,"title":0},"187":{"body":90,"breadcrumbs":0,"title":0},"188":{"body":38,"breadcrumbs":0,"title":0},"189":{"body":11,"breadcrumbs":0,"title":0},"19":{"body":3,"breadcrumbs":0,"title":0},"190":{"body":26,"breadcrumbs":0,"title":0},"191":{"body":3,"breadcrumbs":0,"title":0},"192":{"body":10,"breadcrumbs":0,"title":0},"193":{"body":4,"breadcrumbs":0,"title":0},"194":{"body":308,"breadcrumbs":0,"title":0},"195":{"body":390,"breadcrumbs":1,"title":1},"196":{"body":198,"breadcrumbs":1,"title":1},"197":{"body":196,"breadcrumbs":0,"title":0},"198":{"body":416,"breadcrumbs":1,"title":1},"199":{"body":34,"breadcrumbs":2,"title":2},"2":{"body":14,"breadcrumbs":0,"title":0},"20":{"body":7,"breadcrumbs":2,"title":2},"200":{"body":19,"breadcrumbs":0,"title":0},"201":{"body":10,"breadcrumbs":0,"title":0},"202":{"body":142,"breadcrumbs":0,"title":0},"203":{"body":143,"breadcrumbs":0,"title":0},"204":{"body":101,"breadcrumbs":0,"title":0},"205":{"body":3,"breadcrumbs":0,"title":0},"206":{"body":61,"breadcrumbs":0,"title":0},"207":{"body":320,"breadcrumbs":0,"title":0},"208":{"body":3,"breadcrumbs":0,"title":0},"209":{"body":26,"breadcrumbs":0,"title":0},"21":{"body":34,"breadcrumbs":0,"title":0},"210":{"body":14,"breadcrumbs":0,"title":0},"211":{"body":26,"breadcrumbs":0,"title":0},"212":{"body":23,"breadcrumbs":1,"title":1},"213":{"body":49,"breadcrumbs":0,"title":0},"214":{"body":132,"breadcrumbs":0,"title":0},"215":{"body":17,"breadcrumbs":0,"title":0},"216":{"body":109,"breadcrumbs":0,"title":0},"217":{"body":56,"breadcrumbs":1,"title":1},"218":{"body":191,"breadcrumbs":0,"title":0},"219":{"body":121,"breadcrumbs":1,"title":1},"22":{"body":35,"breadcrumbs":1,"title":1},"220":{"body":90,"breadcrumbs":0,"title":0},"221":{"body":7,"breadcrumbs":0,"title":0},"222":{"body":169,"breadcrumbs":0,"title":0},"223":{"body":176,"breadcrumbs":0,"title":0},"224":{"body":2,"breadcrumbs":0,"title":0},"225":{"body":62,"breadcrumbs":1,"title":1},"226":{"body":249,"breadcrumbs":1,"title":1},"227":{"body":5,"breadcrumbs":0,"title":0},"228":{"body":12,"breadcrumbs":0,"title":0},"229":{"body":46,"breadcrumbs":0,"title":0},"23":{"body":22,"breadcrumbs":1,"title":1},"230":{"body":3,"breadcrumbs":0,"title":0},"231":{"body":6,"breadcrumbs":0,"title":0},"232":{"body":1,"breadcrumbs":0,"title":0},"233":{"body":235,"breadcrumbs":0,"title":0},"234":{"body":80,"breadcrumbs":0,"title":0},"235":{"body":184,"breadcrumbs":1,"title":1},"236":{"body":43,"breadcrumbs":1,"title":1},"237":{"body":134,"breadcrumbs":0,"title":0},"238":{"body":30,"breadcrumbs":0,"title":0},"239":{"body":58,"breadcrumbs":1,"title":1},"24":{"body":52,"breadcrumbs":0,"title":0},"240":{"body":29,"breadcrumbs":0,"title":0},"241":{"body":65,"breadcrumbs":0,"title":0},"242":{"body":71,"breadcrumbs":0,"title":0},"243":{"body":184,"breadcrumbs":1,"title":1},"244":{"body":1,"breadcrumbs":0,"title":0},"245":{"body":167,"breadcrumbs":1,"title":1},"246":{"body":54,"breadcrumbs":0,"title":0},"247":{"body":66,"breadcrumbs":1,"title":1},"248":{"body":4,"breadcrumbs":0,"title":0},"249":{"body":5,"breadcrumbs":1,"title":1},"25":{"body":14,"breadcrumbs":2,"title":2},"250":{"body":63,"breadcrumbs":1,"title":0},"251":{"body":3,"breadcrumbs":2,"title":1},"252":{"body":121,"breadcrumbs":1,"title":0},"253":{"body":175,"breadcrumbs":3,"title":2},"254":{"body":13,"breadcrumbs":2,"title":1},"255":{"body":88,"breadcrumbs":1,"title":0},"256":{"body":38,"breadcrumbs":2,"title":1},"257":{"body":4,"breadcrumbs":1,"title":0},"258":{"body":16,"breadcrumbs":3,"title":2},"259":{"body":2,"breadcrumbs":2,"title":1},"26":{"body":80,"breadcrumbs":1,"title":1},"260":{"body":54,"breadcrumbs":1,"title":0},"261":{"body":419,"breadcrumbs":2,"title":1},"262":{"body":43,"breadcrumbs":3,"title":2},"263":{"body":9,"breadcrumbs":2,"title":1},"264":{"body":4,"breadcrumbs":1,"title":0},"265":{"body":15,"breadcrumbs":0,"title":0},"266":{"body":8,"breadcrumbs":1,"title":1},"267":{"body":18,"breadcrumbs":1,"title":1},"268":{"body":178,"breadcrumbs":0,"title":0},"269":{"body":5,"breadcrumbs":1,"title":1},"27":{"body":102,"breadcrumbs":1,"title":1},"270":{"body":44,"breadcrumbs":0,"title":0},"271":{"body":24,"breadcrumbs":1,"title":1},"272":{"body":50,"breadcrumbs":0,"title":0},"273":{"body":44,"breadcrumbs":1,"title":1},"274":{"body":104,"breadcrumbs":0,"title":0},"275":{"body":18,"breadcrumbs":0,"title":0},"276":{"body":67,"breadcrumbs":1,"title":1},"277":{"body":110,"breadcrumbs":1,"title":1},"278":{"body":8,"breadcrumbs":1,"title":1},"279":{"body":112,"breadcrumbs":1,"title":1},"28":{"body":11,"breadcrumbs":0,"title":0},"280":{"body":80,"breadcrumbs":1,"title":1},"281":{"body":6,"breadcrumbs":1,"title":1},"282":{"body":21,"breadcrumbs":1,"title":1},"283":{"body":324,"breadcrumbs":0,"title":0},"284":{"body":81,"breadcrumbs":1,"title":1},"285":{"body":4,"breadcrumbs":0,"title":0},"286":{"body":194,"breadcrumbs":0,"title":0},"287":{"body":267,"breadcrumbs":1,"title":1},"288":{"body":8,"breadcrumbs":0,"title":0},"289":{"body":7,"breadcrumbs":0,"title":0},"29":{"body":12,"breadcrumbs":1,"title":1},"290":{"body":18,"breadcrumbs":0,"title":0},"291":{"body":76,"breadcrumbs":1,"title":1},"292":{"body":192,"breadcrumbs":1,"title":1},"293":{"body":180,"breadcrumbs":1,"title":1},"294":{"body":97,"breadcrumbs":0,"title":0},"295":{"body":59,"breadcrumbs":0,"title":0},"296":{"body":44,"breadcrumbs":0,"title":0},"297":{"body":62,"breadcrumbs":0,"title":0},"298":{"body":3,"breadcrumbs":0,"title":0},"299":{"body":409,"breadcrumbs":1,"title":1},"3":{"body":1,"breadcrumbs":1,"title":1},"30":{"body":9,"breadcrumbs":0,"title":0},"300":{"body":13,"breadcrumbs":1,"title":1},"301":{"body":3,"breadcrumbs":1,"title":1},"302":{"body":14,"breadcrumbs":1,"title":1},"303":{"body":7,"breadcrumbs":1,"title":1},"304":{"body":6,"breadcrumbs":1,"title":1},"305":{"body":6,"breadcrumbs":0,"title":0},"306":{"body":8,"breadcrumbs":1,"title":1},"307":{"body":4,"breadcrumbs":1,"title":0},"308":{"body":17,"breadcrumbs":1,"title":0},"309":{"body":87,"breadcrumbs":1,"title":0},"31":{"body":4,"breadcrumbs":0,"title":0},"310":{"body":9,"breadcrumbs":1,"title":0},"311":{"body":7,"breadcrumbs":1,"title":0},"312":{"body":19,"breadcrumbs":1,"title":0},"313":{"body":97,"breadcrumbs":1,"title":0},"314":{"body":157,"breadcrumbs":1,"title":0},"315":{"body":5,"breadcrumbs":1,"title":0},"316":{"body":50,"breadcrumbs":1,"title":0},"317":{"body":47,"breadcrumbs":1,"title":0},"318":{"body":41,"breadcrumbs":2,"title":1},"319":{"body":30,"breadcrumbs":1,"title":0},"32":{"body":55,"breadcrumbs":0,"title":0},"320":{"body":24,"breadcrumbs":1,"title":0},"321":{"body":79,"breadcrumbs":1,"title":0},"322":{"body":172,"breadcrumbs":2,"title":1},"323":{"body":180,"breadcrumbs":1,"title":0},"324":{"body":5,"breadcrumbs":1,"title":0},"325":{"body":3,"breadcrumbs":0,"title":0},"326":{"body":1,"breadcrumbs":0,"title":0},"327":{"body":15,"breadcrumbs":1,"title":1},"328":{"body":72,"breadcrumbs":0,"title":0},"329":{"body":22,"breadcrumbs":0,"title":0},"33":{"body":47,"breadcrumbs":0,"title":0},"330":{"body":35,"breadcrumbs":0,"title":0},"331":{"body":71,"breadcrumbs":0,"title":0},"332":{"body":40,"breadcrumbs":0,"title":0},"333":{"body":64,"breadcrumbs":0,"title":0},"334":{"body":0,"breadcrumbs":0,"title":0},"335":{"body":17,"breadcrumbs":0,"title":0},"336":{"body":60,"breadcrumbs":0,"title":0},"337":{"body":19,"breadcrumbs":0,"title":0},"338":{"body":43,"breadcrumbs":0,"title":0},"339":{"body":296,"breadcrumbs":0,"title":0},"34":{"body":53,"breadcrumbs":0,"title":0},"340":{"body":231,"breadcrumbs":0,"title":0},"341":{"body":71,"breadcrumbs":2,"title":2},"342":{"body":118,"breadcrumbs":0,"title":0},"343":{"body":50,"breadcrumbs":0,"title":0},"344":{"body":2,"breadcrumbs":0,"title":0},"345":{"body":8,"breadcrumbs":0,"title":0},"346":{"body":15,"breadcrumbs":2,"title":2},"347":{"body":22,"breadcrumbs":1,"title":1},"348":{"body":71,"breadcrumbs":0,"title":0},"349":{"body":249,"breadcrumbs":1,"title":1},"35":{"body":52,"breadcrumbs":1,"title":1},"350":{"body":53,"breadcrumbs":0,"title":0},"351":{"body":28,"breadcrumbs":1,"title":1},"352":{"body":3,"breadcrumbs":1,"title":1},"353":{"body":2,"breadcrumbs":0,"title":0},"354":{"body":57,"breadcrumbs":0,"title":0},"355":{"body":111,"breadcrumbs":0,"title":0},"356":{"body":278,"breadcrumbs":0,"title":0},"357":{"body":107,"breadcrumbs":0,"title":0},"358":{"body":41,"breadcrumbs":0,"title":0},"359":{"body":1,"breadcrumbs":0,"title":0},"36":{"body":18,"breadcrumbs":1,"title":1},"360":{"body":12,"breadcrumbs":0,"title":0},"361":{"body":156,"breadcrumbs":0,"title":0},"362":{"body":78,"breadcrumbs":1,"title":1},"363":{"body":46,"breadcrumbs":1,"title":1},"364":{"body":0,"breadcrumbs":0,"title":0},"365":{"body":68,"breadcrumbs":0,"title":0},"366":{"body":52,"breadcrumbs":0,"title":0},"367":{"body":2,"breadcrumbs":0,"title":0},"368":{"body":7,"breadcrumbs":0,"title":0},"369":{"body":5,"breadcrumbs":0,"title":0},"37":{"body":25,"breadcrumbs":0,"title":0},"370":{"body":68,"breadcrumbs":1,"title":1},"371":{"body":22,"breadcrumbs":0,"title":0},"372":{"body":300,"breadcrumbs":1,"title":1},"373":{"body":22,"breadcrumbs":0,"title":0},"374":{"body":18,"breadcrumbs":0,"title":0},"375":{"body":1,"breadcrumbs":0,"title":0},"376":{"body":16,"breadcrumbs":1,"title":1},"377":{"body":14,"breadcrumbs":2,"title":1},"378":{"body":72,"breadcrumbs":2,"title":1},"379":{"body":102,"breadcrumbs":1,"title":0},"38":{"body":4,"breadcrumbs":0,"title":0},"380":{"body":39,"breadcrumbs":2,"title":1},"381":{"body":55,"breadcrumbs":1,"title":0},"382":{"body":77,"breadcrumbs":2,"title":1},"383":{"body":129,"breadcrumbs":1,"title":0},"384":{"body":65,"breadcrumbs":1,"title":0},"385":{"body":1,"breadcrumbs":1,"title":0},"386":{"body":67,"breadcrumbs":1,"title":0},"387":{"body":1078,"breadcrumbs":1,"title":0},"388":{"body":7,"breadcrumbs":1,"title":0},"389":{"body":134,"breadcrumbs":2,"title":1},"39":{"body":148,"breadcrumbs":0,"title":0},"390":{"body":462,"breadcrumbs":1,"title":0},"391":{"body":3,"breadcrumbs":1,"title":0},"392":{"body":1,"breadcrumbs":0,"title":0},"393":{"body":1,"breadcrumbs":0,"title":0},"394":{"body":39,"breadcrumbs":0,"title":0},"395":{"body":13,"breadcrumbs":0,"title":0},"396":{"body":55,"breadcrumbs":0,"title":0},"397":{"body":1,"breadcrumbs":1,"title":1},"398":{"body":147,"breadcrumbs":0,"title":0},"399":{"body":135,"breadcrumbs":0,"title":0},"4":{"body":12,"breadcrumbs":0,"title":0},"40":{"body":104,"breadcrumbs":0,"title":0},"400":{"body":11,"breadcrumbs":1,"title":1},"401":{"body":4,"breadcrumbs":1,"title":1},"402":{"body":13,"breadcrumbs":1,"title":1},"403":{"body":15,"breadcrumbs":1,"title":1},"404":{"body":22,"breadcrumbs":1,"title":1},"405":{"body":6,"breadcrumbs":1,"title":1},"406":{"body":8,"breadcrumbs":1,"title":1},"407":{"body":2,"breadcrumbs":1,"title":1},"408":{"body":14,"breadcrumbs":1,"title":1},"409":{"body":75,"breadcrumbs":1,"title":1},"41":{"body":215,"breadcrumbs":0,"title":0},"410":{"body":65,"breadcrumbs":1,"title":1},"411":{"body":21,"breadcrumbs":3,"title":3},"412":{"body":46,"breadcrumbs":2,"title":2},"413":{"body":1,"breadcrumbs":1,"title":1},"414":{"body":4,"breadcrumbs":3,"title":3},"415":{"body":3,"breadcrumbs":0,"title":0},"416":{"body":50,"breadcrumbs":0,"title":0},"417":{"body":4,"breadcrumbs":0,"title":0},"418":{"body":41,"breadcrumbs":1,"title":1},"419":{"body":12,"breadcrumbs":1,"title":1},"42":{"body":101,"breadcrumbs":0,"title":0},"43":{"body":18,"breadcrumbs":0,"title":0},"44":{"body":134,"breadcrumbs":0,"title":0},"45":{"body":6,"breadcrumbs":0,"title":0},"46":{"body":3,"breadcrumbs":0,"title":0},"47":{"body":3,"breadcrumbs":0,"title":0},"48":{"body":102,"breadcrumbs":0,"title":0},"49":{"body":8,"breadcrumbs":1,"title":1},"5":{"body":3,"breadcrumbs":0,"title":0},"50":{"body":73,"breadcrumbs":0,"title":0},"51":{"body":28,"breadcrumbs":0,"title":0},"52":{"body":125,"breadcrumbs":0,"title":0},"53":{"body":144,"breadcrumbs":0,"title":0},"54":{"body":44,"breadcrumbs":0,"title":0},"55":{"body":81,"breadcrumbs":0,"title":0},"56":{"body":74,"breadcrumbs":0,"title":0},"57":{"body":109,"breadcrumbs":0,"title":0},"58":{"body":41,"breadcrumbs":0,"title":0},"59":{"body":1,"breadcrumbs":0,"title":0},"6":{"body":4,"breadcrumbs":0,"title":0},"60":{"body":253,"breadcrumbs":0,"title":0},"61":{"body":155,"breadcrumbs":0,"title":0},"62":{"body":5,"breadcrumbs":0,"title":0},"63":{"body":2,"breadcrumbs":0,"title":0},"64":{"body":4,"breadcrumbs":0,"title":0},"65":{"body":13,"breadcrumbs":0,"title":0},"66":{"body":1,"breadcrumbs":0,"title":0},"67":{"body":20,"breadcrumbs":0,"title":0},"68":{"body":22,"breadcrumbs":1,"title":1},"69":{"body":186,"breadcrumbs":0,"title":0},"7":{"body":2,"breadcrumbs":0,"title":0},"70":{"body":35,"breadcrumbs":0,"title":0},"71":{"body":65,"breadcrumbs":0,"title":0},"72":{"body":87,"breadcrumbs":0,"title":0},"73":{"body":123,"breadcrumbs":0,"title":0},"74":{"body":70,"breadcrumbs":0,"title":0},"75":{"body":0,"breadcrumbs":0,"title":0},"76":{"body":87,"breadcrumbs":0,"title":0},"77":{"body":212,"breadcrumbs":0,"title":0},"78":{"body":14,"breadcrumbs":0,"title":0},"79":{"body":4,"breadcrumbs":0,"title":0},"8":{"body":6,"breadcrumbs":0,"title":0},"80":{"body":5,"breadcrumbs":0,"title":0},"81":{"body":108,"breadcrumbs":0,"title":0},"82":{"body":37,"breadcrumbs":0,"title":0},"83":{"body":73,"breadcrumbs":0,"title":0},"84":{"body":21,"breadcrumbs":0,"title":0},"85":{"body":1,"breadcrumbs":0,"title":0},"86":{"body":48,"breadcrumbs":0,"title":0},"87":{"body":37,"breadcrumbs":0,"title":0},"88":{"body":11,"breadcrumbs":0,"title":0},"89":{"body":24,"breadcrumbs":0,"title":0},"9":{"body":0,"breadcrumbs":0,"title":0},"90":{"body":43,"breadcrumbs":0,"title":0},"91":{"body":116,"breadcrumbs":0,"title":0},"92":{"body":3,"breadcrumbs":0,"title":0},"93":{"body":54,"breadcrumbs":0,"title":0},"94":{"body":50,"breadcrumbs":0,"title":0},"95":{"body":92,"breadcrumbs":0,"title":0},"96":{"body":26,"breadcrumbs":0,"title":0},"97":{"body":35,"breadcrumbs":1,"title":1},"98":{"body":1,"breadcrumbs":0,"title":0},"99":{"body":8,"breadcrumbs":1,"title":1}},"docs":{"0":{"body":"著:Steve Klabnik, Carol Nichols, 貢献:Rustコミュニティ このテキストのこの版では、Rust 2018 Editionのイディオムを使うため、Rust 1.41.0かそれ以降を使っており、すべてのプロジェクトの Cargo.toml に edition=\"2018\" とあることを前提にしています。 Rustをインストールしたりアップデートするには 1章の「インストール」節 を、editionに関しては 付録E を読んでください。 Rust言語の2018 Editionには、Rustをもっと扱いやすく、学びやすくするための多くの改善点があります。 それらの改善点を反映するために、今回の改版は多くの変更点を含んでいます: 7章「肥大化していくプロジェクトをパッケージ、クレート、モジュールを利用して管理する」はほとんど書き直されました。2018 Editionにおいてモジュールシステムとパスの仕組みはより一貫性を持つようになりました。 10章には、新しいimpl Trait構文を説明するために「引数としてのトレイト」と「トレイトを実装している型を返す」という新しい節があります。 11章には、?演算子を使うテストの書き方を説明する「Resultをテストで使う」という新しい節があります。 19章の「高度なライフタイム」節はなくなりました。コンパイラの改善により、この節の内容が現れることはいっそう稀になったからです。 付録D「マクロ」は、手続き的マクロも説明するようになり、19章の「マクロ」節に移動しました。 付録A「キーワード」では、2015 Editionと2018 Editionで書かれたコードを一緒に使えるようにしてくれる、生識別子という新しい機能も説明します。 付録Dは \"Useful Development Tools\" という名前に変わり、Rustコードを書く手助けになる最近登場したツールを説明します。 多くの細かい誤りや不正確な言葉遣いを直しました。報告してくれた読者の皆様、ありがとうございます! edition=\"2018\" を Cargo.toml に書かなければ、Rustコンパイラのバージョンをアップデートしたとしても、 The Rust Programming Language のこれまでの版のコードは変わらずコンパイルできるということを知っておいてください。 Rustの後方互換性の約束は守られています! HTML版は https://doc.rust-lang.org/stable/book/ で公開されています。 オフラインで見たい場合は、rustupでインストールしたRustを使ってrustup docs --bookとしてください。 このテキストの ペーパーバック版と電子書籍版はNo Starch出版 から発売されています。","breadcrumbs":"The Rust Programming Language 日本語版","id":"0","title":"The Rust Programming Language 日本語版"},"1":{"body":"すぐにはわかりにくいかもしれませんが、Rustプログラミング言語は、エンパワーメント(empowerment)を根本原理としています: どんな種類のコードを現在書いているにせよ、Rustは幅広い領域で以前よりも遠くへ到達し、 自信を持ってプログラムを組む力を与え(empower)ます。 一例を挙げると、メモリ管理やデータ表現、並行性などの低レベルな詳細を扱う「システムレベル」のプログラミングがあります。 伝統的にこの分野は難解で、年月をかけてやっかいな落とし穴を回避する術を習得した選ばれし者にだけ可能と見なされています。 そのように鍛錬を積んだ者でさえ注意が必要で、さもないと書いたコードがクラッキングの糸口になったりクラッシュやデータ破損を引き起こしかねないのです。 この難しさを取り除くために、Rustは、古い落とし穴を排除し、その過程で使いやすく役に立つ洗練された一連のツールを提供します。 低レベルな制御に「下がる」必要があるプログラマは、お決まりのクラッシュやセキュリティホールのリスクを負わず、 気まぐれなツールチェーンのデリケートな部分を学ぶ必要なくRustで同じことができます。さらにいいことに、 Rustは、スピードとメモリ使用の観点で効率的な信頼性の高いコードへと自然に導くよう設計されています。 既に低レベルコードに取り組んでいるプログラマは、Rustを使用してさらなる高みを目指せます。例えば、 Rustで並列性を導入することは、比較的低リスクです: コンパイラが伝統的なミスを捕捉してくれるのです。 そして、クラッシュやクラッキングの糸口を誤って導入しないという自信を持ってコードの大胆な最適化に取り組めるのです。 ですが、Rustは低レベルなシステムプログラミングに限定されているわけではありません。十分に表現力豊かでエルゴノミックなので、 コマンドラインアプリやWebサーバ、その他様々な楽しいコードを書けます。この本の後半に両者の単純な例が見つかるでしょう。 Rustを使うことで1つの領域から他の領域へと使い回せる技術を身につけられます; ウェブアプリを書いてRustを学び、それからその同じ技術をラズベリーパイを対象に適用できるのです。 この本は、ユーザに力を与え(empower)るRustのポテンシャルを全て含んでいます。あなたのRustの知識のみをレベルアップさせるだけでなく、 プログラマとしての全般的な能力や自信をもレベルアップさせる手助けを意図した親しみやすくわかりやすいテキストです。 さあ、飛び込んで学ぶ準備をしてください。Rustコミュニティへようこそ! ニコラス・マットサキス(Nicholas Matsakis)とアーロン・チューロン(Aaron Turon)","breadcrumbs":"まえがき","id":"1","title":"まえがき"},"10":{"body":"一般的に、この本は、順番に読み進めていくことを前提にしています。後の章は、前の章の概念の上に成り立ち、 前の章では、ある話題にさほど深入りしない可能性があります; 典型的に後ほどの章で同じ話題を再度しています。 この本には2種類の章があるとわかるでしょう: 概念の章とプロジェクトの章です。概念の章では、 Rustの一面を学ぶでしょう。プロジェクトの章では、それまでに学んだことを適用して一緒に小さなプログラムを構築します。 2、12、20章がプロジェクトの章です。つまり、残りは概念の章です。 第1章はRustのインストール方法、Hello, world!プログラムの書き方、Rustのパッケージマネージャ兼、 ビルドツールのCargoの使用方法を説明します。第2章は、Rust言語への実践的な導入です。概念を高度に講義し、後ほどの章で追加の詳細を提供します。 今すぐRustの世界に飛び込みたいなら、第2章こそがそのためのものです。第3章は他のプログラミング言語の機能に似たRustの機能を講義していますが、 最初その3章すら飛ばして、まっすぐに第4章に向かい、Rustの所有権システムについて学びたくなる可能性があります。 しかしながら、あなたが次に進む前に全ての詳細を学ぶことを好む特別に几帳面な学習者なら、 第2章を飛ばして真っ先に第3章に行き、学んだ詳細を適用するプロジェクトに取り組みたくなった時に第2章に戻りたくなる可能性があります。 第5章は、構造体とメソッドについて議論し、第6章はenum、match式、if letフロー制御構文を講義します。 構造体とenumを使用してRustにおいて独自の型を作成します。 第7章では、Rustのモジュールシステムと自分のコードとその公開されたAPI(Application Programming Interface)を体系化するプライバシー規則について学びます。 第8章では、ベクタ、文字列、ハッシュマップなどの標準ライブラリが提供する一般的なコレクションデータ構造の一部を議論します。 第9章では、Rustのエラー処理哲学とテクニックを探究します。 第10章ではジェネリクス、トレイト、ライフタイムについて深入りし、これらは複数の型に適用されるコードを定義する力をくれます。 第11章は、完全にテストに関してで、Rustの安全性保証があってさえ、プログラムのロジックが正しいことを保証するために、 必要になります。第12章では、ファイル内のテキストを検索するgrepコマンドラインツールの一部の機能を自身で構築します。 このために、以前の章で議論した多くの概念を使用します。 第13章はクロージャとイテレータを探究します。これらは、関数型プログラミング言語由来のRustの機能です。 第14章では、Cargoをより詳しく調査し、他人と自分のライブラリを共有する最善の策について語ります。 第15章では、標準ライブラリが提供するスマートポインタとその機能を可能にするトレイトを議論します。 第16章では、並行プログラミングの異なるモデルを見ていき、Rustが恐れなしに複数のスレッドでプログラムする手助けをする方法を語ります。 第17章では、馴染み深い可能性のあるオブジェクト指向プログラミングの原則とRustのイディオムがどう比較されるかに目を向けます。 第18章は、パターンとパターンマッチングのリファレンスであり、これらはRustプログラムを通して、 考えを表現する強力な方法になります。第19章は、unsafe Rustやライフタイム、トレイト、型、関数、クロージャの詳細を含む、 興味のある高度な話題のスモーガスボード(訳注: 日本でいうバイキングのこと)を含みます。 第20章では、低レベルなマルチスレッドのWebサーバを実装するプロジェクトを完成させます! 最後に、言語についての有用な情報をよりリファレンスのような形式で含む付録があります。 付録AはRustのキーワードを講義し、付録Bは、Rustの演算子と記号、付録Cは、 標準ライブラリが提供する導出可能なトレイト、付録Dはマクロを講義します。 この本を読む間違った方法なんてありません: 飛ばしたければ、どうぞご自由に! 混乱したら、前の章に戻らなければならない可能性もあります。ですが、自分に合った方法でどうぞ。 Rustを学ぶ過程で重要な部分は、コンパイラが表示するエラーメッセージを読む方法を学ぶことです: それは動くコードへと導いてくれます。そのため、各場面でコンパイラが表示するエラーメッセージとともに、 コンパイルできないコードの例を多く提供します。適当に例を選んで走らせたら、コンパイルできないかもしれないことを知ってください! 周りのテキストを読んで実行しようとしている例がエラーになることを意図しているのか確認することを確かめてください。 ほとんどの場合、コンパイルできないあらゆるコードの正しいバージョンへと導きます。","breadcrumbs":"この本の使い方","id":"10","title":"この本の使い方"},"100":{"body":"コードで表現したくなるかもしれない場面に目を向けて、enumが有用でこの場合、構造体よりも適切である理由を確認しましょう。 IPアドレスを扱う必要が出たとしましょう。現在、IPアドレスの規格は二つあります: バージョン4とバージョン6です。 これらは、プログラムが遭遇するIPアドレスのすべての可能性です: 列挙型は、取りうる値をすべて 列挙 でき、 これが列挙型の名前の由来です。 どんなIPアドレスも、バージョン4かバージョン6のどちらかになりますが、同時に両方にはなり得ません。 IPアドレスのその特性により、enumデータ構造が適切なものになります。というのも、 enumの値は、その列挙子のいずれか一つにしかなり得ないからです。バージョン4とバージョン6のアドレスは、 どちらも根源的にはIPアドレスですから、コードがいかなる種類のIPアドレスにも適用される場面を扱う際には、 同じ型として扱われるべきです。 この概念をコードでは、IpAddrKind列挙型を定義し、IPアドレスがなりうる種類、V4とV6を列挙することで、 表現できます。これらは、enumの 列挙子 として知られています: enum IpAddrKind { V4, V6,\n} これで、IpAddrKindはコードの他の場所で使用できる独自のデータ型になります。","breadcrumbs":"Enumとパターンマッチング » Enumを定義する","id":"100","title":"Enumを定義する"},"101":{"body":"以下のようにして、IpAddrKindの各列挙子のインスタンスは生成できます: # enum IpAddrKind {\n# V4,\n# V6,\n# }\n#\nlet four = IpAddrKind::V4;\nlet six = IpAddrKind::V6; enumの列挙子は、その識別子の元に名前空間分けされていることと、 2連コロンを使ってその二つを区別していることに注意してください。 これが有効な理由は、こうすることで、値IpAddrKind::V4とIpAddrKind::V6という値は両方とも、 同じ型IpAddrKindになったからです。そうしたら、例えば、どんなIpAddrKindを取る関数も定義できるようになります。 # enum IpAddrKind {\n# V4,\n# V6,\n# }\n#\nfn route(ip_type: IpAddrKind) { } そして、この関数をどちらの列挙子に対しても呼び出せます: # enum IpAddrKind {\n# V4,\n# V6,\n# }\n#\n# fn route(ip_type: IpAddrKind) { }\n#\nroute(IpAddrKind::V4);\nroute(IpAddrKind::V6); enumの利用には、さらなる利点さえもあります。このIPアドレス型についてもっと考えてみると、現状では、 実際のIPアドレスの データ を保持する方法がありません。つまり、どんな 種類 であるかを知っているだけです。 構造体について第5章で学んだばっかりとすると、この問題に対して、あなたはリスト6-1のように対処するかもしれません。 enum IpAddrKind { V4, V6,\n} struct IpAddr { kind: IpAddrKind, address: String,\n} let home = IpAddr { kind: IpAddrKind::V4, address: String::from(\"127.0.0.1\"),\n}; let loopback = IpAddr { kind: IpAddrKind::V6, address: String::from(\"::1\"),\n}; リスト6-1: IPアドレスのデータとIpAddrKindの列挙子をstructを使って保持する ここでは、二つのフィールドを持つIpAddrという構造体を定義しています: IpAddrKind型(先ほど定義したenumですね)のkindフィールドと、 String型のaddressフィールドです。この構造体のインスタンスが2つあります。最初のインスタンス、 homeにはkindとしてIpAddrKind::V4があり、紐付けられたアドレスデータは127.0.0.1です。 2番目のインスタンス、loopbackには、kindの値として、IpAddrKindのもう一つの列挙子、V6があり、 アドレス::1が紐付いています。構造体を使ってkindとaddress値を一緒に包んだので、 もう列挙子は値と紐付けられています。 各enumの列挙子に直接データを格納して、enumを構造体内に使うというよりもenumだけを使って、 同じ概念をもっと簡潔な方法で表現することができます。この新しいIpAddrの定義は、 V4とV6列挙子両方にString値が紐付けられていることを述べています。 enum IpAddr { V4(String), V6(String),\n} let home = IpAddr::V4(String::from(\"127.0.0.1\")); let loopback = IpAddr::V6(String::from(\"::1\")); enumの各列挙子にデータを直接添付できるので、余計な構造体を作る必要は全くありません。 構造体よりもenumを使うことには、別の利点もあります: 各列挙子に紐付けるデータの型と量は、異なってもいいのです。 バージョン4のIPアドレスには、常に0から255の値を持つ4つの数値があります。V4のアドレスは、4つのu8型の値として格納するけれども、 V6のアドレスは引き続き、単独のString型の値で格納したかったとしても、構造体では不可能です。 enumなら、こんな場合も容易に対応できます: enum IpAddr { V4(u8, u8, u8, u8), V6(String),\n} let home = IpAddr::V4(127, 0, 0, 1); let loopback = IpAddr::V6(String::from(\"::1\")); バージョン4とバージョン6のIPアドレスを格納するデータ構造を定義する複数の異なる方法を示してきました。 しかしながら、蓋を開けてみれば、IPアドレスを格納してその種類をコード化したくなるということは一般的なので、 標準ライブラリに使用可能な定義があります! 標準ライブラリでのIpAddrの定義のされ方を見てみましょう: 私たちが定義し、使用したのと全く同じenumと列挙子がありますが、アドレスデータを二種の異なる構造体の形で列挙子に埋め込み、 この構造体は各列挙子用に異なる形で定義されています。 struct Ipv4Addr { // 省略\n} struct Ipv6Addr { // 省略\n} enum IpAddr { V4(Ipv4Addr), V6(Ipv6Addr),\n} このコードは、enum列挙子内にいかなる種類のデータでも格納できることを描き出しています: 例を挙げれば、文字列、数値型、構造体などです。他のenumを含むことさえできます!また、 標準ライブラリの型は、あなたの想像するよりも複雑ではないことがしばしばあります。 標準ライブラリにIpAddrに対する定義は含まれるものの、標準ライブラリの定義をまだ我々のスコープに導入していないので、 干渉することなく自分自身の定義を生成して使用できることに注意してください。型をスコープに導入することについては、 第7章でもっと詳しく言及します。 リスト6-2でenumの別の例を見てみましょう: 今回のコードは、幅広い種類の型が列挙子に埋め込まれています。 enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32),\n} リスト6-2: 列挙子各々が異なる型と量の値を格納するMessage enum このenumには、異なる型の列挙子が4つあります: Quitには紐付けられたデータは全くなし。 Moveは、中に匿名構造体を含む。 Writeは、単独のStringオブジェクトを含む。 ChangeColorは、3つのi32値を含む。 リスト6-2のような列挙子を含むenumを定義することは、enumの場合、structキーワードを使わず、 全部の列挙子がMessage型の元に分類される点を除いて、異なる種類の構造体定義を定義するのと類似しています。 以下の構造体も、先ほどのenumの列挙子が保持しているのと同じデータを格納することができるでしょう: struct QuitMessage; // ユニット構造体\nstruct MoveMessage { x: i32, y: i32,\n}\nstruct WriteMessage(String); // タプル構造体\nstruct ChangeColorMessage(i32, i32, i32); // タプル構造体 ですが、異なる構造体を使っていたら、各々、それ自身の型があるので、単独の型になるリスト6-2で定義したMessage enumほど、 これらの種のメッセージいずれもとる関数を簡単に定義することはできないでしょう。 enumと構造体にはもう1点似通っているところがあります: implを使って構造体にメソッドを定義できるのと全く同様に、 enumにもメソッドを定義することができるのです。こちらは、Message enum上に定義できるcallという名前のメソッドです: # enum Message {\n# Quit,\n# Move { x: i32, y: i32 },\n# Write(String),\n# ChangeColor(i32, i32, i32),\n# }\n#\nimpl Message { fn call(&self) { // method body would be defined here // メソッド本体はここに定義される }\n} let m = Message::Write(String::from(\"hello\"));\nm.call(); メソッドの本体では、selfを使用して、メソッドを呼び出した相手の値を取得できるでしょう。この例では、 Message::Write(String::from(\"hello\"))という値を持つ、変数mを生成したので、これがm.call()を走らせた時に、 callメソッドの本体内でselfが表す値になります。 非常に一般的で有用な別の標準ライブラリのenumを見てみましょう: Optionです。","breadcrumbs":"Enumとパターンマッチング » Enumの値","id":"101","title":"Enumの値"},"102":{"body":"前節で、IpAddr enumがRustの型システムを使用して、プログラムにデータ以上の情報をコード化できる方法を目撃しました。 この節では、Optionのケーススタディを掘り下げていきます。この型も標準ライブラリにより定義されているenumです。 このOption型はいろんな箇所で使用されます。なぜなら、値が何かかそうでないかという非常に一般的な筋書きをコード化するからです。 この概念を型システムの観点で表現することは、コンパイラが、プログラマが処理すべき場面全てを処理していることをチェックできることを意味します; この機能は、他の言語において、究極的にありふれたバグを阻止することができます。 プログラミング言語のデザインは、しばしばどの機能を入れるかという観点で考えられるが、 除いた機能も重要なのです。Rustには、他の多くの言語にはあるnull機能がありません。 null とはそこに何も値がないことを意味する値です。nullのある言語において、 変数は常に二者択一どちらかの状態になります: nullかそうでないかです。 nullの開発者であるトニー・ホーア(Tony Hoare)の2009年のプレゼンテーション、 \"Null References: The Billion Dollar Mistake\"(Null参照: 10億ドルの間違い)では、こんなことが語られています。 私はそれを10億ドルの失敗と呼んでいます。その頃、私は、オブジェクト指向言語の参照に対する、 最初のわかりやすい型システムを設計していました。私の目標は、 どんな参照の使用も全て完全に安全であるべきことを、コンパイラにそのチェックを自動で行ってもらって保証することだったのです。 しかし、null参照を入れるという誘惑に打ち勝つことができませんでした。それは、単純に実装が非常に容易だったからです。 これが無数のエラーや脆弱性、システムクラッシュにつながり、過去40年で10億ドルの苦痛や損害を引き起こしたであろうということなのです。 null値の問題は、nullの値をnullでない値のように使用しようとしたら、何らかの種類のエラーが出ることです。 このnullかそうでないかという特性は広く存在するので、この種の間違いを大変犯しやすいのです。 しかしながら、nullが表現しようとしている概念は、それでも役に立つものです: nullは、 何らかの理由で現在無効、または存在しない値のことなのです。 問題は、全く概念にあるのではなく、特定の実装にあるのです。そんな感じなので、Rustにはnullがありませんが、 値が存在するか不在かという概念をコード化するenumならあります。このenumがOptionで、 以下のように 標準ライブラリに定義 されています。 enum Option { Some(T), None,\n} Optionは有益すぎて、初期化処理(prelude)にさえ含まれています。つまり、明示的にスコープに導入する必要がないのです。 さらに、列挙子もそうなっています: SomeとNoneをOption::の接頭辞なしに直接使えるわけです。 ただ、Optionはそうは言っても、普通のenumであり、Some(T)とNoneもOption型のただの列挙子です。 という記法は、まだ語っていないRustの機能です。これは、ジェネリック型引数であり、ジェネリクスについて詳しくは、 第10章で解説します。とりあえず、知っておく必要があることは、は、Option enumのSome列挙子が、 あらゆる型のデータを1つだけ持つことができることを意味していることだけです。こちらは、 Option値を使って、数値型や文字列型を保持する例です。 let some_number = Some(5);\nlet some_string = Some(\"a string\"); let absent_number: Option = None; Someではなく、Noneを使ったら、コンパイラにOptionの型が何になるかを教えなければいけません。 というのも、None値を見ただけでは、Some列挙子が保持する型をコンパイラが推論できないからです。 Some値がある時、値が存在するとわかり、その値は、Someに保持されています。None値がある場合、 ある意味、nullと同じことを意図します: 有効な値がないのです。では、なぜOptionの方が、 nullよりも少しでも好ましいのでしょうか? 簡潔に述べると、OptionとT(ここでTはどんな型でもいい)は異なる型なので、 コンパイラがOption値を確実に有効な値かのようには使用させてくれません。 例えば、このコードはi8をOptionに足そうとしているので、コンパイルできません。 let x: i8 = 5;\nlet y: Option = Some(5); let sum = x + y; このコードを動かしたら、以下のようなエラーメッセージが出ます。 error[E0277]: the trait bound `i8: std::ops::Add>` is\nnot satisfied\n(エラー: `i8: std::ops::Add>`というトレイト境界が満たされていません) --> |\n5 | let sum = x + y; | ^ no implementation for `i8 + std::option::Option` | なんて強烈な!実際に、このエラーメッセージは、i8とOptionが異なる型なので、 足し合わせる方法がコンパイラにはわからないことを意味します。Rustにおいて、i8のような型の値がある場合、 コンパイラが常に有効な値であることを確認してくれます。この値を使う前にnullであることをチェックする必要なく、 自信を持って先に進むことができるのです。Optionがある時(あるいはどんな型を扱おうとしていても)のみ、 値を保持していない可能性を心配する必要があるわけであり、 コンパイラはプログラマが値を使用する前にそのような場面を扱っているか確かめてくれます。 言い換えると、T型の処理を行う前には、OptionをTに変換する必要があるわけです。一般的に、 これにより、nullの最もありふれた問題の一つを捕捉する一助になります: 実際にはnullなのに、 そうでないかのように想定することです。 不正確にnullでない値を想定する心配をしなくてもよいということは、コード内でより自信を持てることになります。 nullになる可能性のある値を保持するには、その値の型をOptionにすることで明示的に同意しなければなりません。 それからその値を使用する際には、値がnullである場合を明示的に処理する必要があります。 値がOption以外の型であるとこ全てにおいて、値がnullでないと安全に想定することが できます 。 これは、Rustにとって、意図的な設計上の決定であり、nullの普遍性を制限し、Rustコードの安全性を向上させます。 では、Option型の値がある時、その値を使えるようにするには、どのようにSome列挙子からT型の値を取り出せばいいのでしょうか? Optionには様々な場面で有効に活用できる非常に多くのメソッドが用意されています; ドキュメント でそれらを確認できます。Optionのメソッドに馴染むと、 Rustの旅が極めて有益になるでしょう。 一般的に、Option値を使うには、各列挙子を処理するコードが欲しくなります。 Some(T)値がある時だけ走る何らかのコードが欲しくなり、このコードが内部のTを使用できます。 None値があった場合に走る別のコードが欲しくなり、そちらのコードはT値は使用できない状態になります。 match式が、enumとともに使用した時にこれだけの動作をするフロー制御文法要素になります: enumの列挙子によって、違うコードが走り、そのコードがマッチした値の中のデータを使用できるのです。","breadcrumbs":"Enumとパターンマッチング » Option enumとNull値に勝る利点","id":"102","title":"Option enumとNull値に勝る利点"},"103":{"body":"Rustには、一連のパターンに対して値を比較し、マッチしたパターンに応じてコードを実行させてくれるmatchと呼ばれる、 非常に強力なフロー制御演算子があります。パターンは、リテラル値、変数名、ワイルドカードやその他多数のもので構成することができます; 第18章で、全ての種類のパターンと、その目的については解説します。matchのパワーは、 パターンの表現力とコンパイラが全てのありうるパターンを処理しているかを確認してくれるという事実に由来します。 match式をコイン並べ替え装置のようなものと考えてください: コインは、様々なサイズの穴が空いた通路を流れ落ち、 各コインは、サイズのあった最初の穴に落ちます。同様に、値はmatchの各パターンを通り抜け、値が「適合する」最初のパターンで、 値は紐付けられたコードブロックに落ち、実行中に使用されるわけです。 コインについて話したので、それをmatchを使用する例にとってみましょう!数え上げ装置と同じ要領で未知のアメリカコインを一枚取り、 どの種類のコインなのか決定し、その価値をセントで返す関数をリスト6-3で示したように記述することができます。 enum Coin { Penny, Nickel, Dime, Quarter,\n} fn value_in_cents(coin: Coin) -> u32 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter => 25, }\n} リスト6-3: enumとそのenumの列挙子をパターンにしたmatch式 value_in_cents関数内のmatchを噛み砕きましょう。まず、matchキーワードに続けて式を並べています。 この式は今回の場合、値coinです。ifで使用した式と非常に酷似しているみたいですね。しかし、大きな違いがあります: ifでは、式は論理値を返す必要がありますが、ここでは、どんな型でも構いません。この例におけるcoinの型は、 1行目で定義したCoin enumです。 次は、matchアームです。一本のアームには2つの部品があります: パターンと何らかのコードです。 今回の最初のアームはCoin::Pennyという値のパターンであり、パターンと動作するコードを区別する=>演算子が続きます。 この場合のコードは、ただの値1です。各アームは次のアームとカンマで区切られています。 このmatch式が実行されると、結果の値を各アームのパターンと順番に比較します。パターンに値がマッチしたら、 そのコードに紐付けられたコードが実行されます。パターンが値にマッチしなければ、コイン並べ替え装置と全く同じように、 次のアームが継続して実行されます。必要なだけパターンは存在できます: リスト6-3では、matchには4本のアームがあります。 各アームに紐付けられるコードは式であり、マッチしたアームの式の結果がmatch式全体の戻り値になります。 典型的に、アームのコードが短い場合、波かっこは使用されません。リスト6-3では、各アームが値を返すだけなので、 これに倣っています。マッチのアームで複数行のコードを走らせたいのなら、波かっこを使用することができます。 例えば、以下のコードは、メソッドがCoin::Pennyとともに呼び出されるたびに「Lucky penny!」と表示しつつ、 ブロックの最後の値、1を返すでしょう。 # enum Coin {\n# Penny,\n# Nickel,\n# Dime,\n# Quarter,\n# }\n#\nfn value_in_cents(coin: Coin) -> u32 { match coin { Coin::Penny => { println!(\"Lucky penny!\"); 1 }, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter => 25, }\n}","breadcrumbs":"Enumとパターンマッチング » matchフロー制御演算子","id":"103","title":"matchフロー制御演算子"},"104":{"body":"マッチのアームの別の有益な機能は、パターンにマッチした値の一部に束縛できる点です。こうして、 enumの列挙子から値を取り出すことができます。 例として、enumの列挙子の一つを中にデータを保持するように変えましょう。1999年から2008年まで、 アメリカは、片側に50の州それぞれで異なるデザインをしたクォーターコインを鋳造していました。 他のコインは州のデザインがなされることはなかったので、クォーターだけがこのおまけの値を保持します。 Quarter列挙子を変更して、UsState値が中に保持されるようにすることでenumにこの情報を追加でき、 それをしたのがリスト6-4のコードになります。 #[derive(Debug)] // すぐに州を点検できるように\nenum UsState { Alabama, Alaska, // ... などなど\n} enum Coin { Penny, Nickel, Dime, Quarter(UsState),\n} リスト6-4: Quarter列挙子がUsStateの値も保持するCoin enum 友人の一人が50州全部のクォーターコインを収集しようとしているところを想像しましょう。コインの種類で小銭を並べ替えつつ、 友人が持っていない種類だったら、コレクションに追加できるように、各クォーターに関連した州の名前を出力します。 このコードのmatch式では、Coin::Quarter列挙子の値にマッチするstateという名の変数をパターンに追加します。 Coin::Quarterがマッチすると、state変数はそのクォーターのstateの値に束縛されます。それから、 stateをそのアームのコードで使用できます。以下のようにですね: # #[derive(Debug)]\n# enum UsState {\n# Alabama,\n# Alaska,\n# }\n#\n# enum Coin {\n# Penny,\n# Nickel,\n# Dime,\n# Quarter(UsState),\n# }\n#\nfn value_in_cents(coin: Coin) -> u32 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter(state) => { println!(\"State quarter from {:?}!\", state); 25 }, }\n} value_in_cents(Coin::Quarter(UsState::Alaska))と呼び出すつもりだったなら、coinは Coin::Quarter(UsState::Alaska)になります。その値をmatchの各アームと比較すると、 Coin::Quarter(state)に到達するまで、どれにもマッチしません。その時に、stateに束縛されるのは、 UsState::Alaskaという値です。そして、println!式でその束縛を使用することができ、 そのため、Coin enumの列挙子からQuarterに対する中身のstateの値を取得できたわけです。","breadcrumbs":"Enumとパターンマッチング » 値に束縛されるパターン","id":"104","title":"値に束縛されるパターン"},"105":{"body":"前節では、Optionを使用する際に、Someケースから中身のTの値を取得したくなりました。要するに、 Coin enumに対して行ったように、matchを使ってOptionを扱うこともできるというわけです! コインを比較する代わりに、Optionの列挙子を比較するのですが、match式の動作の仕方は同じままです。 Optionを取る関数を書きたくなったとし、中に値があったら、その値に1を足すことにしましょう。 中に値がなければ、関数はNone値を返し、何も処理を試みるべきではありません。 matchのおかげで、この関数は大変書きやすく、リスト6-5のような見た目になります。 fn plus_one(x: Option) -> Option { match x { None => None, Some(i) => Some(i + 1), }\n} let five = Some(5);\nlet six = plus_one(five);\nlet none = plus_one(None); リスト6-5: Optionにmatch式を使う関数 plus_oneの最初の実行についてもっと詳しく検証しましょう。plus_one(five)と呼び出した時、 plus_oneの本体の変数xはSome(5)になります。そして、これをマッチの各アームと比較します。 None => None, Some(5)という値は、Noneというパターンにはマッチしませんので、次のアームに処理が移ります。 Some(i) => Some(i + 1), Some(5)はSome(i)にマッチしますか?なんと、します!列挙子が同じです。iはSomeに含まれる値に束縛されるので、 iは値5になります。それから、このマッチのアームのコードが実行されるので、iの値に1を足し、 合計の6を中身にした新しいSome値を生成します。 さて、xがNoneになるリスト6-5の2回目のplus_oneの呼び出しを考えましょう。matchに入り、 最初のアームと比較します。 None => None, マッチします!足し算する値がないので、プログラムは停止し、=>の右辺にあるNone値が返ります。 最初のアームがマッチしたため、他のアームは比較されません。 matchとenumの組み合わせは、多くの場面で有効です。Rustコードにおいて、このパターンはよく見かけるでしょう: enumに対しmatchし、内部のデータに変数を束縛させ、それに基づいたコードを実行します。最初はちょっと巧妙ですが、 一旦慣れてしまえば、全ての言語にあってほしいと願うことになるでしょう。一貫してユーザのお気に入りなのです。","breadcrumbs":"Enumとパターンマッチング » Optionとのマッチ","id":"105","title":"Optionとのマッチ"},"106":{"body":"もう一つ議論する必要のあるmatchの観点があります。一点バグがありコンパイルできないこんなバージョンのplus_one関数を考えてください: fn plus_one(x: Option) -> Option { match x { Some(i) => Some(i + 1), }\n} Noneの場合を扱っていないため、このコードはバグを生みます。幸い、コンパイラが捕捉できるバグです。 このコードのコンパイルを試みると、こんなエラーが出ます: error[E0004]: non-exhaustive patterns: `None` not covered\n(エラー: 包括的でないパターン: `None`がカバーされてません) --> |\n6 | match x { | ^ pattern `None` not covered 全可能性を網羅していないことをコンパイラは検知しています。もっと言えば、どのパターンを忘れているかさえ知っているのです。 Rustにおけるマッチは、 包括的 です: 全てのあらゆる可能性を網羅し尽くさなければ、コードは有効にならないのです。 特にOptionの場合には、私達が明示的にNoneの場合を処理するのを忘れないようにしてくれます。 nullになるかもしれないのに値があると思い込まないよう、すなわち前に議論した10億ドルの失敗を犯さないよう、 コンパイラが保護してくれるわけです。","breadcrumbs":"Enumとパターンマッチング » マッチは包括的","id":"106","title":"マッチは包括的"},"107":{"body":"Rustには、全ての可能性を列挙したくない時に使用できるパターンもあります。例えば、u8は、有効な値として、 0から255までを取ります。1、3、5、7の値にだけ興味があったら、0、2、4、6、8、9と255までの数値を列挙する必要に迫られたくはないです。 幸運なことに、する必要はありません: 代わりに特別なパターンの_を使用できます: let some_u8_value = 0u8;\nmatch some_u8_value { 1 => println!(\"one\"), 3 => println!(\"three\"), 5 => println!(\"five\"), 7 => println!(\"seven\"), _ => (),\n} _というパターンは、どんな値にもマッチします。他のアームの後に記述することで、_は、 それまでに指定されていない全ての可能性にマッチします。()は、ただのユニット値なので、_の場合には、 何も起こりません。結果として、_プレースホルダーの前に列挙していない可能性全てに対しては、 何もしたくないと言えるわけです。 ですが、 一つ のケースにしか興味がないような場面では、match式はちょっと長ったらしすぎます。 このような場面用に、Rustには、if letが用意されています。","breadcrumbs":"Enumとパターンマッチング » _というプレースホルダー","id":"107","title":"_というプレースホルダー"},"108":{"body":"if let記法でifとletをより冗長性の少ない方法で組み合わせ、残りを無視しつつ、一つのパターンにマッチする値を扱うことができます。 Optionにマッチするけれど、値が3の時にだけコードを実行したい、リスト6-6のプログラムを考えてください。 let some_u8_value = Some(0u8);\nmatch some_u8_value { Some(3) => println!(\"three\"), _ => (),\n} リスト6-6: 値がSome(3)の時だけコードを実行するmatch Some(3)にマッチした時だけ何かをし、他のSome値やNone値の時には何もしたくありません。 match式を満たすためには、列挙子を一つだけ処理した後に_ => ()を追加しなければなりません。 これでは、追加すべき定型コードが多すぎます。 その代わり、if letを使用してもっと短く書くことができます。以下のコードは、 リスト6-6のmatchと同じように振る舞います: # let some_u8_value = Some(0u8);\nif let Some(3) = some_u8_value { println!(\"three\");\n} if letという記法は等号記号で区切られたパターンと式を取り、式がmatchに与えられ、パターンが最初のアームになったmatchと、 同じ動作をします。 if letを使うと、タイプ数が減り、インデントも少なくなり、定型コードも減ります。しかしながら、 matchでは強制された包括性チェックを失ってしまいます。matchかif letかの選択は、 特定の場面でどんなことをしたいかと簡潔性を得ることが包括性チェックを失うのに適切な代償となるかによります。 言い換えると、if letは値が一つのパターンにマッチした時にコードを走らせ、 他は無視するmatchへの糖衣構文と考えることができます。 if letでは、elseを含むこともできます。elseに入るコードブロックは、 if letとelseに等価なmatch式の_の場合に入るコードブロックと同じになります。 リスト6-4のCoin enum定義を思い出してください。ここでは、Quarter列挙子は、 UsStateの値も保持していましたね。クォーターコインの状態を告げつつ、 見かけたクォーター以外のコインの枚数を数えたいなら、以下のようにmatch式で実現することができるでしょう: # #[derive(Debug)]\n# enum UsState {\n# Alabama,\n# Alaska,\n# }\n#\n# enum Coin {\n# Penny,\n# Nickel,\n# Dime,\n# Quarter(UsState),\n# }\n# let coin = Coin::Penny;\nlet mut count = 0;\nmatch coin { // {:?}州のクォーターコイン Coin::Quarter(state) => println!(\"State quarter from {:?}!\", state), _ => count += 1,\n} または、以下のようにif letとelseを使うこともできるでしょう: # #[derive(Debug)]\n# enum UsState {\n# Alabama,\n# Alaska,\n# }\n#\n# enum Coin {\n# Penny,\n# Nickel,\n# Dime,\n# Quarter(UsState),\n# }\n# let coin = Coin::Penny;\nlet mut count = 0;\nif let Coin::Quarter(state) = coin { println!(\"State quarter from {:?}!\", state);\n} else { count += 1;\n} matchを使って表現するには冗長的すぎるロジックがプログラムにあるようなシチュエーションに遭遇したら、 if letもRust道具箱にあることを思い出してください。","breadcrumbs":"Enumとパターンマッチング » if letで簡潔なフロー制御","id":"108","title":"if letで簡潔なフロー制御"},"109":{"body":"これで、enumを使用してワンセットの列挙された値のどれかになりうる独自の型を生成する方法を講義しました。 標準ライブラリのOptionが型システムを使用して、エラーを回避する際に役立つ方法についても示しました。 enumの値がデータを内部に含む場合、処理すべきケースの数に応じて、matchかif letを使用して値を取り出し、 使用できます。 もうRustプログラムで構造体とenumを使用して、自分の領域の概念を表現できます。API内で使用するために独自の型を生成することで、 型安全性を保証することができます: コンパイラが、各関数の予期する型の値のみを関数が得ることを確かめてくれるのです。 使用するのに率直な整理整頓されたAPIをユーザに提供し、ユーザが必要とするものだけを公開するために、 今度は、Rustのモジュールに目を向けてみましょう。","breadcrumbs":"Enumとパターンマッチング » まとめ","id":"109","title":"まとめ"},"11":{"body":"この本が生成されるソースファイルは、 GitHub で見つかります。 訳注: 日本語版は こちら です。","breadcrumbs":"ソースコード","id":"11","title":"ソースコード"},"110":{"body":"大きなプログラムを書く時、そのすべてを頭の中に入れておくのは不可能になるため、コードのまとまりを良くすることが重要になります。 関係した機能をまとめ、異なる特徴を持つコードを分割することにより、特定の機能を実装しているコードを見つけたり、機能を変更したりするためにどこを探せば良いのかを明確にできます。 私達がこれまでに書いてきたプログラムは、一つのファイル内の一つのモジュール内にありました。 プロジェクトが大きくなるにつれて、これを複数のモジュールに、ついで複数のファイルに分割することで、プログラムを整理することができます。 パッケージは複数のバイナリクレートからなり、またライブラリクレートを1つもつこともできます。 パッケージが大きくなるにつれて、その一部を抜き出して分離したクレートにし、外部依存とするのもよいでしょう。 この章ではそれらのテクニックすべてを学びます。 相互に関係し合い、同時に成長するパッケージの集まりからなる巨大なプロジェクトには、 Cargoがワークスペースという機能を提供します。これは14章の Cargoワークスペース で解説します。 機能をグループにまとめられることに加え、実装の詳細がカプセル化されることにより、コードをより高いレベルで再利用できるようになります: 手続きを実装し終えてしまえば、他のコードはそのコードの公開されたインターフェースを通じて、実装の詳細を知ることなくそのコードを呼び出すことができるのです。 コードをどう書くかによって、どの部分が他のコードにも使える公開のものになるのか、それとも自分だけが変更できる非公開のものになるのかが決定されます。 これもまた、記憶しておくべき細部を制限してくれる方法のひとつです。 関係する概念にスコープがあります: コードが記述されているネストされた文脈には、「スコープ内」として定義される名前の集合があります。 コードを読んだり書いたりコンパイルしたりする時には、プログラマーやコンパイラは特定の場所にある特定の名前が、変数・関数・構造体・enum・モジュール・定数・その他のどの要素を表すのか、そしてその要素は何を意味するのかを知る必要があります。 そこでスコープを作り、どの名前がスコープ内/スコープ外にあるのかを変更することができます。 同じ名前のものを2つ同じスコープ内に持つことはできません。そこで、名前の衝突を解決するための方法があります。 Rustには、どの詳細を公開するか、どの詳細を非公開にするか、どの名前がプログラムのそれぞれのスコープにあるか、といったコードのまとまりを保つためのたくさんの機能があります。 これらの機能は、まとめて「モジュールシステム」と呼ばれることがあり、以下のようなものが含まれます。 パッケージ: クレートをビルドし、テストし、共有することができるCargoの機能 クレート: ライブラリか実行可能ファイルを生成する、木構造をしたモジュール群 モジュール と use: これを使うことで、パスの構成、スコープ、公開するか否かを決定できます パス: 要素(例えば構造体や関数やモジュール)に名前をつける方法 この章では、これらの機能をすべて学び、これらがどう相互作用するかについて議論し、これらをどう使ってスコープを制御するのかについて説明します。 この章を読み終わる頃には、モジュールシステムをしっかりと理解し、熟練者のごとくスコープを扱うことができるようになっているでしょう!","breadcrumbs":"肥大化していくプロジェクトをパッケージ、クレート、モジュールを利用して管理する","id":"110","title":"肥大化していくプロジェクトをパッケージ、クレート、モジュールを利用して管理する"},"111":{"body":"最初に学ぶモジュールシステムの要素は、パッケージとクレートです。 クレートはバイナリかライブラリのどちらかです。 クレートルート (crate root) とは、Rustコンパイラの開始点となり、クレートのルートモジュールを作るソースファイルのことです(モジュールについて詳しくは 「モジュールを定義して、スコープとプライバシーを制御する」 のセクションで説明します)。 パッケージ はある機能群を提供する1つ以上のクレートです。 パッケージは Cargo.toml という、それらのクレートをどのようにビルドするかを説明するファイルを持っています。 パッケージが何を持ってよいかはいくつかのルールで決まっています。 パッケージは0個か1個のライブラリクレートを持っていないといけません。それ以上は駄目です。 バイナリクレートはいくらでも持って良いですが、少なくとも(ライブラリでもバイナリでも良いですが)1つのクレートを持っていないといけません。 パッケージを作る時に何が起こるか見てみましょう。 まず、cargo newというコマンドを入力します: $ cargo new my-project Created binary (application) `my-project` package\n$ ls my-project\nCargo.toml\nsrc\n$ ls my-project/src\nmain.rs このコマンドを入力したとき、Cargoは Cargo.toml ファイルを作り、パッケージを作ってくれました。 Cargo.toml の中身を見ても、 src/main.rs については何も書いてありません。これは、Cargoは src/main.rs が、パッケージと同じ名前を持つバイナリクレートのクレートルートであるという慣習に従っているためです。 同じように、Cargoはパッケージディレクトリに src/lib.rs が含まれていたら、パッケージにはパッケージと同じ名前のライブラリクレートが含まれており、 src/lib.rs がそのクレートルートなのだと判断します。 Cargoはクレートルートファイルを rustcに渡し、ライブラリやバイナリをビルドします。 今、このパッケージには src/main.rs しか含まれておらず、つまりこのパッケージはmy-projectという名前のバイナリクレートのみを持っているということです。 もしパッケージが src/main.rs と src/lib.rs を持っていたら、クレートは2つになります:どちらもパッケージと同じ名前を持つ、ライブラリクレートとバイナリクレートです。 ファイルを src/bin ディレクトリに置くことで、パッケージは複数のバイナリクレートを持つことができます。それぞれのファイルが別々のバイナリクレートになります。 クレートは、関連した機能を一つのスコープにまとめることで、その機能が複数のプロジェクト間で共有しやすいようにします。 例えば、 2章 で使ったrandクレートは、乱数を生成する機能を提供します。 randクレートを私達のプロジェクトのスコープに持ち込むことで、この機能を私達のプロジェクトで使うことができます。 randクレートが提供する機能にはすべて、クレートの名前randを使ってアクセスできます。 クレートの機能をそれ自身のスコープの中に入れたままにしておくことは、ある機能が私達のクレートで定義されたのかrandクレートで定義されたのかを明確にし、名前の衝突を予防してくれます。 例えば、randクレートはRngという名前のトレイトを提供しています。 更に、私達のクレートでRngという名前のstructを定義することもできます。 クレートの機能はそのスコープ内の名前空間に位置づけられているので、randを依存先として追加しても、コンパイラはRngという名前が何を意味するのかについて混乱することはないのです。 私達のクレートでは、私達の定義したstruct Rngのことであり、randクレートのRngトレイトにはrand::Rngでアクセスするというわけです。 では、モジュールシステムの話に移りましょう!","breadcrumbs":"肥大化していくプロジェクトをパッケージ、クレート、モジュールを利用して管理する » パッケージとクレート","id":"111","title":"パッケージとクレート"},"112":{"body":"この節では、モジュールと、その他のモジュールシステムの要素 ――すなわち、要素に名前をつけるための パス 、パスをスコープに持ち込むuseキーワード、要素を公開するpubキーワード―― について学びます。 また、asキーワード、外部パッケージ、glob演算子についても話します。 とりあえず、今はモジュールに集中しましょう! モジュール はクレート内のコードをグループ化し、可読性と再利用性を上げるのに役に立ちます。 モジュールは要素の プライバシー も制御できます。プライバシーとは、要素がコードの外側で使える (公開 public) のか、内部の実装の詳細であり外部では使えない (非公開 private) のかです。 例えば、レストランの機能を提供するライブラリクレートを書いてみましょう。 実際にレストランを実装することではなく、コードの関係性に注目したいので、関数にシグネチャをつけますが中身は空白のままにします。 レストラン業界では、レストランの一部を 接客部門 (front of house) といい、その他を 後方部門 (back of house) といいます。 接客部門とはお客さんがいるところです。接客係がお客様を席に案内し、給仕係が注文と支払いを受け付け、バーテンダーが飲み物を作ります。 後方部門とはシェフや料理人がキッチンで働き、皿洗い係が食器を片付け、マネージャが管理業務をする場所です。 私達のクレートを現実のレストランと同じような構造にするために、関数をネストしたモジュールにまとめましょう。 restaurantという名前の新しいライブラリをcargo new --lib restaurantと実行することで作成し、Listing 7-1 のコードを src/lib.rs に書き込み、モジュールと関数のシグネチャを定義してください。 ファイル名: src/lib.rs mod front_of_house { mod hosting { fn add_to_waitlist() {} fn seat_at_table() {} } mod serving { fn take_order() {} fn serve_order() {} fn take_payment() {} }\n}\n# # fn main() {} Listing 7-1: front_of_houseモジュールにその他のモジュールが含まれ、さらにそれらが関数を含んでいる モジュールは、modキーワードを書き、次にモジュールの名前(今回の場合、front_of_house)を指定することで定義されます。 モジュールの中には、今回だとhostingとservingのように、他のモジュールをおくこともできます。 モジュールにはその他の要素の定義も置くことができます。例えば、構造体、enum、定数、トレイト、そして(Listing 7-1のように)関数です。 モジュールを使うことで、関連する定義を一つにまとめ、関連する理由を名前で示せます。 このコードを使うプログラマーは、定義を全部読むことなく、グループ単位でコードを読み進められるので、欲しい定義を見つけ出すのが簡単になるでしょう。 このコードに新しい機能を付け加えるプログラマーは、プログラムのまとまりを保つために、どこにその機能のコードを置けば良いのかがわかるでしょう。 以前、 src/main.rs と src/lib.rs はクレートルートと呼ばれていると言いました。 この名前のわけは、 モジュールツリー と呼ばれるクレートのモジュール構造の根っこ (ルート)にこれら2つのファイルの中身がcrateというモジュールを形成するからです。 Listing 7-2は、Listing 7-1の構造のモジュールツリーを示しています。 crate └── front_of_house ├── hosting │ ├── add_to_waitlist │ └── seat_at_table └── serving ├── take_order ├── serve_order └── take_payment Listing 7-2: Listing 7-1 のコードのモジュールツリー このツリーを見ると、どのモジュールがどのモジュールの中にネストしているのかがわかります(例えば、hostingはfront_of_houseの中にネストしています)。 また、いくつかのモジュールはお互いに 兄弟 の関係にある、つまり、同じモジュール内で定義されていることもわかります(例えばhostingとservingはfront_of_houseで定義されています)。 他にも、家族関係の比喩を使って、モジュールAがモジュールBの中に入っている時、AはBの 子 であるといい、BはAの 親 であるといいます。 モジュールツリー全体が、暗黙のうちに作られたcrateというモジュールの下にあることにも注目してください。 モジュールツリーを見ていると、コンピュータのファイルシステムのディレクトリツリーを思い出すかもしれません。その喩えはとても適切です! ファイルシステムのディレクトリのように、モジュールはコードをまとめるのに使われるのです。 そしてディレクトリからファイルを見つけるように、目的のモジュールを見つけ出す方法が必要になります。","breadcrumbs":"肥大化していくプロジェクトをパッケージ、クレート、モジュールを利用して管理する » モジュールを定義して、スコープとプライバシーを制御する","id":"112","title":"モジュールを定義して、スコープとプライバシーを制御する"},"113":{"body":"ファイルシステムの中を移動する時と同じように、Rustにモジュールツリー内の要素を見つけるためにはどこを探せばいいのか教えるためにパスを使います。 関数を呼び出したいなら、そのパスを知っていなければなりません。 パスは2つの形を取ることができます: 絶対パス は、クレートの名前かcrateという文字列を使うことで、クレートルートからスタートします。 相対パス は、self、superまたは今のモジュール内の識別子を使うことで、現在のモジュールからスタートします。 絶対パスも相対パスも、その後に一つ以上の識別子がダブルコロン(::)で仕切られて続きます。 Listing 7-1の例に戻ってみましょう。 add_to_waitlist関数をどうやって呼べばいいでしょうか? すなわち、add_to_waitlistのパスは何でしょうか? Listing 7-3 は、モジュールと関数をいくつか取り除いてコードをやや簡潔にしています。 これを使って、クレートルートに定義された新しいeat_at_restaurantという関数から、add_to_waitlist関数を呼びだす2つの方法を示しましょう。 eat_at_restaurant関数はこのライブラリクレートの公開 (public) APIの1つなので、pubキーワードをつけておきます。 pubについては、 パスをpubキーワードで公開する の節でより詳しく学びます。 この例はまだコンパイルできないことに注意してください。理由はすぐに説明します。 ファイル名: src/lib.rs mod front_of_house { mod hosting { fn add_to_waitlist() {} }\n} pub fn eat_at_restaurant() { // Absolute path // 絶対パス crate::front_of_house::hosting::add_to_waitlist(); // Relative path // 相対パス front_of_house::hosting::add_to_waitlist();\n} Listing 7-3: add_to_waitlist 関数を絶対パスと相対パスで呼び出す eat_at_restaurantで最初にadd_to_waitlist関数を呼び出す時、絶対パスを使っています。 add_to_waitlist関数はeat_at_restaurantと同じクレートで定義されているので、crateキーワードで絶対パスを始めることができます。 crateの後は、add_to_waitlistにたどり着くまで、後に続くモジュールを書き込んでいます。 同じ構造のファイルシステムを想像すれば、/front_of_house/hosting/add_to_waitlistとパスを指定してadd_to_waitlistを実行していることに相当します。 crateという名前を使ってクレートルートからスタートするというのは、/を使ってファイルシステムのルートからスタートするようなものです。 eat_at_restaurantで2回目にadd_to_waitlist関数を呼び出す時、相対パスを使っています。 パスは、モジュールツリーにおいてeat_at_restaurantと同じ階層で定義されているモジュールであるfront_of_houseからスタートします。 これはファイルシステムでfront_of_house/hosting/add_to_waitlistというパスを使っているのに相当します。 名前から始めるのは、パスが相対パスであることを意味します。 相対パスを使うか絶対パスを使うかは、プロジェクトによって決めましょう。 要素を定義するコードを、その要素を使うコードと別々に動かすか一緒に動かすか、どちらが起こりそうかによって決めるのが良いです。 例えば、front_of_houseモジュールとeat_at_restaurant関数をcustomer_experienceというモジュールに移動させると、add_to_waitlistへの絶対パスを更新しないといけませんが、相対パスは有効なままです。 しかし、eat_at_restaurant関数だけをdiningというモジュールに移動させると、add_to_waitlistへの絶対パスは同じままですが、相対パスは更新しないといけないでしょう。 コードの定義と、その要素の呼び出しは独立に動かしそうなので、絶対パスのほうが好ましいです。 では、Listing 7-3 をコンパイルしてみて、どうしてこれはまだコンパイルできないのか考えてみましょう! エラーをListing 7-4 に示しています。 $ cargo build Compiling restaurant v0.1.0 (file:///projects/restaurant)\nerror[E0603]: module `hosting` is private --> src/lib.rs:9:28 |\n9 | crate::front_of_house::hosting::add_to_waitlist(); | ^^^^^^^ error[E0603]: module `hosting` is private --> src/lib.rs:12:21 |\n12 | front_of_house::hosting::add_to_waitlist(); | ^^^^^^^ error: aborting due to 2 previous errors For more information about this error, try `rustc --explain E0603`.\nerror: could not compile `restaurant`. To learn more, run the command again with --verbose. Listing 7-4: Listing 7-3のコードをビルドしたときのコンパイルエラー エラーメッセージは、hostingは非公開 (private) だ、と言っています。 言い換えるなら、hostingモジュールとadd_to_waitlist関数へのパスは正しいが、非公開な部分へのアクセスは許可されていないので、Rustがそれを使わせてくれないということです。 モジュールはコードの整理に役立つだけではありません。 モジュールはRustの プライバシー境界 も定義します。これは、外部のコードが知ったり、呼び出したり、依存したりしてはいけない実装の詳細をカプセル化する線引きです。 なので、関数や構造体といった要素を非公開にしたければ、モジュールに入れればよいのです。 Rustにおけるプライバシーは、「あらゆる要素(関数、メソッド、構造体、enum、モジュールおよび定数)は標準では非公開」というやり方で動いています。 親モジュールの要素は子モジュールの非公開要素を使えませんが、子モジュールの要素はその祖先モジュールの要素を使えます。 これは、子モジュールは実装の詳細を覆い隠しますが、子モジュールは自分の定義された文脈を見ることができるためです。 レストランの喩えを続けるなら、レストランの後方部門になったつもりでプライバシーのルールを考えてみてください。レストランの顧客にはそこで何が起こっているのかは非公開ですが、そこで働くオフィスマネージャには、レストランのことは何でも見えるし何でもできるのです。 Rustは、内部実装の詳細を隠すことが標準であるようにモジュールシステムを機能させることを選択しました。 こうすることで、内部のコードのどの部分が、外部のコードを壊すことなく変更できるのかを知ることができます。 しかし、pubキーワードを使って要素を公開することで、子モジュールの内部部品を外部の祖先モジュールに見せることができます。","breadcrumbs":"肥大化していくプロジェクトをパッケージ、クレート、モジュールを利用して管理する » モジュールツリーの要素を示すためのパス","id":"113","title":"モジュールツリーの要素を示すためのパス"},"114":{"body":"Listing 7-4の、hostingモジュールが非公開だと言ってきていたエラーに戻りましょう。 親モジュールのeat_at_restaurant関数が子モジュールのadd_to_waitlist関数にアクセスできるようにしたいので、hostingモジュールにpubキーワードをつけます。Listing 7-5のようになります。 ファイル名: src/lib.rs mod front_of_house { pub mod hosting { fn add_to_waitlist() {} }\n} pub fn eat_at_restaurant() { // Absolute path // 絶対パス crate::front_of_house::hosting::add_to_waitlist(); // Relative path // 相対パス front_of_house::hosting::add_to_waitlist();\n} Listing 7-5: hosting モジュールを pub として宣言することでeat_at_restaurantから使う 残念ながら、Listing 7-5 のコードもListing 7-6 に示されるようにエラーとなります。 $ cargo build Compiling restaurant v0.1.0 (file:///projects/restaurant)\nerror[E0603]: function `add_to_waitlist` is private --> src/lib.rs:9:37 |\n9 | crate::front_of_house::hosting::add_to_waitlist(); | ^^^^^^^^^^^^^^^ error[E0603]: function `add_to_waitlist` is private --> src/lib.rs:12:30 |\n12 | front_of_house::hosting::add_to_waitlist(); | ^^^^^^^^^^^^^^^ error: aborting due to 2 previous errors For more information about this error, try `rustc --explain E0603`.\nerror: could not compile `restaurant`. To learn more, run the command again with --verbose. Listing 7-6: Listing 7-5 のコードをビルドしたときのコンパイルエラー 何が起きたのでしょう?pubキーワードをmod hostingの前に追加したことで、このモジュールは公開されました。 この変更によって、front_of_houseにアクセスできるなら、hostingにもアクセスできるようになりました。 しかしhostingの 中身 はまだ非公開です。モジュールを公開してもその中身は公開されないのです。 モジュールにpubキーワードがついていても、祖先モジュールのコードはモジュールを参照できるようになるだけです。 Listing 7-6 のエラーはadd_to_waitlist関数が非公開だと言っています。 プライバシーのルールは、モジュール同様、構造体、enum、関数、メソッドにも適用されるのです。 add_to_waitlistの定義の前にpubキーワードを追加して、これも公開しましょう。 ファイル名: src/lib.rs mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} }\n} pub fn eat_at_restaurant() { // Absolute path // 絶対パス crate::front_of_house::hosting::add_to_waitlist(); // Relative path // 相対パス front_of_house::hosting::add_to_waitlist();\n}\n# # fn main() {} Listing 7-7: pubキーワードをmod hostingとfn add_to_waitlistに追加することで、eat_at_restaurantからこの関数を呼べるようになる これでこのコードはコンパイルできます! 絶対パスと相対パスをもう一度確認して、どうしてpubキーワードを追加することでadd_to_waitlistのそれらのパスを使えるようになるのか、プライバシールールの観点からもう一度確認してみてみましょう。 絶対パスは、クレートのモジュールツリーのルートであるcrateから始まります。 クレートルートの中にfront_of_houseが定義されています。 front_of_houseは公開されていませんが、eat_at_restaurant関数はfront_of_houseと同じモジュール内で定義されている(つまり、eat_at_restaurantとfront_of_houseは兄弟な)ので、eat_at_restaurantからfront_of_houseを参照することができます。 次はpubの付いたhostingモジュールです。 hostingの親モジュールにアクセスできるので、hostingにもアクセスできます。 最後に、add_to_waitlist関数はpubが付いており、私達はその親モジュールにアクセスできるので、この関数呼び出しはうまく行くというわけです。 相対パスについても、最初のステップを除けば同じ理屈です。パスをクレートルートから始めるのではなくて、front_of_houseから始めるのです。 front_of_houseモジュールはeat_at_restaurantと同じモジュールで定義されているので、eat_at_restaurantが定義されている場所からの相対パスが使えます。 そして、hostingとadd_to_waitlistはpubが付いていますから、残りのパスについても問題はなく、この関数呼び出しは有効というわけです。","breadcrumbs":"肥大化していくプロジェクトをパッケージ、クレート、モジュールを利用して管理する » パスをpubキーワードで公開する","id":"114","title":"パスをpubキーワードで公開する"},"115":{"body":"親モジュールから始まる相対パスなら、superを最初につけることで構成できます。 ファイルシステムパスを..構文で始めるのに似ています。 どのようなときのこの機能が使いたくなるのでしょう? シェフが間違った注文を修正し、自分でお客さんに持っていくという状況をモデル化している、Listing 7-8 を考えてみてください。 fix_incorrect_order関数はserve_order関数を呼び出すために、superから始まるserve_order関数へのパスを使っています。 ファイル名: src/lib.rs fn serve_order() {} mod back_of_house { fn fix_incorrect_order() { cook_order(); super::serve_order(); } fn cook_order() {}\n}\n# # fn main() {} Listing 7-8: super で始まる相対パスを使って関数を呼び出す fix_incorrect_order関数はback_of_houseモジュールの中にあるので、superを使ってback_of_houseの親モジュールにいけます。親モジュールは、今回の場合ルートであるcrateです。 そこから、serve_orderを探し、見つけ出します。 成功! もしクレートのモジュールツリーを再編成することにした場合でも、back_of_houseモジュールとserve_order関数は同じ関係性で有り続け、一緒に動くように思われます。 そのため、superを使うことで、将来このコードが別のモジュールに移動するとしても、更新する場所が少なくて済むようにしました。","breadcrumbs":"肥大化していくプロジェクトをパッケージ、クレート、モジュールを利用して管理する » 相対パスをsuperで始める","id":"115","title":"相対パスをsuperで始める"},"116":{"body":"構造体やenumもpubを使って公開するよう指定できますが、追加の細目がいくつかあります。 構造体定義の前にpubを使うと、構造体は公開されますが、構造体のフィールドは非公開のままなのです。 それぞれのフィールドを公開するか否かを個々に決められます。 Listing 7-9 では、公開のtoastフィールドと、非公開のseasonal_fruitフィールドをもつ公開のback_of_house::Breakfast構造体を定義しました。 これは、例えば、レストランで、お客さんが食事についてくるパンの種類は選べるけれど、食事についてくるフルーツは季節と在庫に合わせてシェフが決める、という状況をモデル化しています。 提供できるフルーツはすぐに変わるので、お客さんはフルーツを選ぶどころかどんなフルーツが提供されるのか知ることもできません。 ファイル名: src/lib.rs mod back_of_house { pub struct Breakfast { pub toast: String, seasonal_fruit: String, } impl Breakfast { pub fn summer(toast: &str) -> Breakfast { Breakfast { toast: String::from(toast), seasonal_fruit: String::from(\"peaches\"), } } }\n} pub fn eat_at_restaurant() { // Order a breakfast in the summer with Rye toast // 夏 (summer) にライ麦 (Rye) パン付き朝食を注文 let mut meal = back_of_house::Breakfast::summer(\"Rye\"); // Change our mind about what bread we'd like // やっぱり別のパンにする meal.toast = String::from(\"Wheat\"); println!(\"I'd like {} toast please\", meal.toast); // The next line won't compile if we uncomment it; we're not allowed // to see or modify the seasonal fruit that comes with the meal // 下の行のコメントを外すとコンパイルできない。食事についてくる // 季節のフルーツを知ることも修正することも許されていないので // meal.seasonal_fruit = String::from(\"blueberries\");\n} Listing 7-9: 公開のフィールドと非公開のフィールドとを持つ構造体 back_of_house::Breakfastのtoastフィールドは公開されているので、eat_at_restaurantにおいてtoastをドット記法を使って読み書きできます。 seasonal_fruitは非公開なので、eat_at_restaurantにおいてseasonal_fruitは使えないということに注意してください。 seasonal_fruitを修正している行のコメントを外して、どのようなエラーが得られるか試してみてください! また、back_of_house::Breakfastは非公開のフィールドを持っているので、Breakfastのインスタンスを作成 (construct) する公開された関連関数が構造体によって提供されている必要があります(ここではsummerと名付けました)。 もしBreakfastにそのような関数がなかったら、eat_at_restaurantにおいて非公開であるseasonal_fruitの値を設定できないので、Breakfastのインスタンスを作成できません。 一方で、enumを公開すると、そのヴァリアントはすべて公開されます。 Listing 7-10 に示されているように、pubはenumキーワードの前にだけおけばよいのです。 ファイル名: src/lib.rs mod back_of_house { pub enum Appetizer { Soup, Salad, }\n} pub fn eat_at_restaurant() { let order1 = back_of_house::Appetizer::Soup; let order2 = back_of_house::Appetizer::Salad;\n} Listing 7-10: enumを公開に指定することはそのヴァリアントをすべて公開にする Appetizerというenumを公開したので、SoupとSaladというヴァリアントもeat_at_restaurantで使えます。 enumはヴァリアントが公開されてないとあまり便利ではないのですが、毎回enumのすべてのヴァリアントにpubをつけるのは面倒なので、enumのヴァリアントは標準で公開されるようになっているのです。 構造体はフィールドが公開されていなくても便利なことが多いので、構造体のフィールドは、pubがついてない限り標準で非公開という通常のルールに従うわけです。 まだ勉強していない、pubの関わるシチュエーションがもう一つあります。モジュールシステムの最後の機能、useキーワードです。 use自体の勉強をした後、pubとuseを組み合わせる方法についてお見せします。","breadcrumbs":"肥大化していくプロジェクトをパッケージ、クレート、モジュールを利用して管理する » 構造体とenumを公開する","id":"116","title":"構造体とenumを公開する"},"117":{"body":"これまで関数呼び出しのために書いてきたパスは、長く、繰り返しも多くて不便なものでした。 例えば、Listing 7-7 においては、絶対パスを使うか相対パスを使うかにかかわらず、add_to_waitlist関数を呼ぼうと思うたびにfront_of_houseとhostingも指定しないといけませんでした。 ありがたいことに、この手続きを簡単化する方法があります。 useキーワードを使うことで、パスを一度スコープに持ち込んでしまえば、それ以降はパス内の要素がローカルにあるかのように呼び出すことができるのです。 Listing 7-11 では、crate::front_of_house::hostingモジュールをeat_at_restaurant関数のスコープに持ち込むことで、eat_at_restaurantにおいて、hosting::add_to_waitlistと指定するだけでadd_to_waitlist関数を呼び出せるようにしています。 ファイル名: src/lib.rs mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} }\n} use crate::front_of_house::hosting; pub fn eat_at_restaurant() { hosting::add_to_waitlist(); hosting::add_to_waitlist(); hosting::add_to_waitlist();\n}\n# # fn main() {} Listing 7-11: use でモジュールをスコープに持ち込む useとパスをスコープに追加することは、ファイルシステムにおいてシンボリックリンクを張ることに似ています。 use crate::front_of_house::hostingをクレートルートに追加することで、hostingはこのスコープで有効な名前となり、まるでhostingはクレートルートで定義されていたかのようになります。 スコープにuseで持ち込まれたパスも、他のパスと同じようにプライバシーがチェックされます。 useと相対パスで要素をスコープに持ち込むこともできます。 Listing 7-12 はListing 7-11 と同じふるまいを得るためにどう相対パスを書けば良いかを示しています。 ファイル名: src/lib.rs mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} }\n} use self::front_of_house::hosting; pub fn eat_at_restaurant() { hosting::add_to_waitlist(); hosting::add_to_waitlist(); hosting::add_to_waitlist();\n}\n# # fn main() {} Listing 7-12: モジュールをuseと相対パスを使ってスコープに持ち込む","breadcrumbs":"肥大化していくプロジェクトをパッケージ、クレート、モジュールを利用して管理する » useキーワードでパスをスコープに持ち込む","id":"117","title":"useキーワードでパスをスコープに持ち込む"},"118":{"body":"Listing 7-11 を見て、なぜuse crate::front_of_house::hostingと書いてeat_at_restaurant内でhosting::add_to_waitlistと呼び出したのか不思議に思っているかもしれません。Listing 7-13 のように、useでadd_to_waitlistまでのパスをすべて指定しても同じ結果が得られるのに、と。 ファイル名: src/lib.rs mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} }\n} use crate::front_of_house::hosting::add_to_waitlist; pub fn eat_at_restaurant() { add_to_waitlist(); add_to_waitlist(); add_to_waitlist();\n}\n# # fn main() {} Listing 7-13: add_to_waitlist 関数をuse でスコープに持ち込む。このやりかたは慣例的ではない Listing 7-11 も 7-13 もおなじ仕事をしてくれますが、関数をスコープにuseで持ち込む場合、Listing 7-11 のほうが慣例的なやり方です。 関数の親モジュールをuseで持ち込むことで、関数を呼び出す際、毎回親モジュールを指定しなければならないようにすれば、フルパスを繰り返して書くことを抑えつつ、関数がローカルで定義されていないことを明らかにできます。 Listing 7-13 のコードではどこでadd_to_waitlistが定義されたのかが不明瞭です。 一方で、構造体やenumその他の要素をuseで持ち込むときは、フルパスを書くのが慣例的です。 Listing 7-14 は標準ライブラリのHashMap構造体をバイナリクレートのスコープに持ち込む慣例的なやり方を示しています。 ファイル名: src/main.rs use std::collections::HashMap; fn main() { let mut map = HashMap::new(); map.insert(1, 2);\n} Listing 7-14: HashMapを慣例的なやり方でスコープに持ち込む こちらの慣例の背後には、はっきりとした理由はありません。自然に発生した慣習であり、みんなRustのコードをこのやり方で読み書きするのに慣れてしまったというだけです。 同じ名前の2つの要素をuseでスコープに持ち込むのはRustでは許されないので、そのときこの慣例は例外的に不可能です。 Listing 7-15は、同じ名前を持つけれど異なる親モジュールを持つ2つのResult型をスコープに持ち込み、それらを参照するやり方を示しています。 ファイル名: src/lib.rs use std::fmt;\nuse std::io; fn function1() -> fmt::Result { // --snip-- // (略)\n# Ok(())\n} fn function2() -> io::Result<()> { // --snip-- // (略)\n# Ok(())\n} Listing 7-15: 同じ名前を持つ2つの型を同じスコープに持ち込むには親モジュールを使わないといけない。 このように、親モジュールを使うことで2つのResult型を区別できます。 もしuse std::fmt::Result と use std::io::Resultと書いていたとしたら、2つのResult型が同じスコープに存在することになり、私達がResultを使ったときにどちらのことを意味しているのかRustはわからなくなってしまいます。","breadcrumbs":"肥大化していくプロジェクトをパッケージ、クレート、モジュールを利用して管理する » 慣例に従ったuseパスを作る","id":"118","title":"慣例に従ったuseパスを作る"},"119":{"body":"同じ名前の2つの型をuseを使って同じスコープに持ち込むという問題には、もう一つ解決策があります。パスの後に、asと型の新しいローカル名、即ちエイリアスを指定すればよいのです。 Listing 7-16 は、Listing 7-15 のコードを、2つのResult型のうち一つをasを使ってリネームするという別のやり方で書いたものを表しています。 ファイル名: src/lib.rs use std::fmt::Result;\nuse std::io::Result as IoResult; fn function1() -> Result { // --snip--\n# Ok(())\n} fn function2() -> IoResult<()> { // --snip--\n# Ok(())\n} Listing 7-16: 型がスコープに持ち込まれた時、asキーワードを使ってその名前を変えている 2つめのuse文では、std::io::Resultに、IoResultという新たな名前を選んでやります。std::fmtのResultもスコープに持ち込んでいますが、この名前はこれとは衝突しません。 Listing 7-15もListing 7-16も慣例的とみなされているので、どちらを使っても構いませんよ!","breadcrumbs":"肥大化していくプロジェクトをパッケージ、クレート、モジュールを利用して管理する » 新しい名前をasキーワードで与える","id":"119","title":"新しい名前をasキーワードで与える"},"12":{"body":"Rustの旅を始めましょう!学ぶべきことはたくさんありますが、いかなる旅もどこかから始まります。 この章では、以下のことを議論します: RustをLinux、macOS、Windowsにインストールする Hello, world!と出力するプログラムを書く cargoというRustのパッケージマネージャ兼ビルドシステムを使用する","breadcrumbs":"事始め","id":"12","title":"事始め"},"120":{"body":"useキーワードで名前をスコープに持ちこんだ時、新しいスコープで使用できるその名前は非公開です。 私達のコードを呼び出すコードが、まるでその名前が私達のコードのスコープで定義されていたかのように参照できるようにするためには、pubとuseを組み合わせればいいです。 このテクニックは、要素を自分たちのスコープに持ち込むだけでなく、他の人がその要素をその人のスコープに持ち込むことも可能にすることから、 再公開 (re-exporting) と呼ばれています。 Listing 7-17 は Listing 7-11 のコードのルートモジュールでのuseをpub useに変更したものを示しています。 ファイル名: src/lib.rs mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} }\n} pub use crate::front_of_house::hosting; pub fn eat_at_restaurant() { hosting::add_to_waitlist(); hosting::add_to_waitlist(); hosting::add_to_waitlist();\n}\n# # fn main() {} Listing 7-17: pub useで、新たなスコープのコードがその名前を使えるようにする pub useを使うことで、外部のコードがhosting::add_to_waitlistを使ってadd_to_waitlist関数を呼び出せるようになりました。 pub useを使っていなければ、eat_at_restaurant関数はhosting::add_to_waitlistを自らのスコープ内で使えるものの、外部のコードはこの新しいパスを利用することはできないでしょう。 再公開は、あなたのコードの内部構造と、あなたのコードを呼び出すプログラマーたちのその領域に関しての見方が異なるときに有用です。 例えば、レストランの比喩では、レストランを経営している人は「接客部門 (front of house)」と「後方部門 (back of house)」のことについて考えるでしょう。 しかし、レストランを訪れるお客さんは、そのような観点からレストランの部門について考えることはありません。 pub useを使うことで、ある構造でコードを書きつつも、別の構造で公開するということが可能になります。 こうすることで、私達のライブラリを、ライブラリを開発するプログラマにとっても、ライブラリを呼び出すプログラマにとっても、よく整理されたものとすることができます。","breadcrumbs":"肥大化していくプロジェクトをパッケージ、クレート、モジュールを利用して管理する » pub useを使って名前を再公開する","id":"120","title":"pub useを使って名前を再公開する"},"121":{"body":"2章で、乱数を得るためにrandという外部パッケージを使って、数当てゲームをプログラムしました。 randを私達のプロジェクトで使うために、次の行を Cargo.toml に書き加えましたね: ファイル名: Cargo.toml [dependencies]\nrand = \"0.5.5\" randを依存 (dependency) として Cargo.toml に追加すると、randパッケージとそのすべての依存を crates.io からダウンロードして、私達のプロジェクトでrandが使えるようにするようCargoに命令します。 そして、randの定義を私達のパッケージのスコープに持ち込むために、クレートの名前であるrandから始まるuseの行を追加し、そこにスコープに持ち込みたい要素を並べました。 2章の 乱数を生成する の節で、Rngトレイトをスコープに持ち込みrand::thread_rng関数を呼び出したことを思い出してください。 # use std::io;\nuse rand::Rng; fn main() {\n# println!(\"Guess the number!\");\n# let secret_number = rand::thread_rng().gen_range(1, 101);\n# # println!(\"The secret number is: {}\", secret_number);\n# # println!(\"Please input your guess.\");\n# # let mut guess = String::new();\n# # io::stdin()\n# .read_line(&mut guess)\n# .expect(\"Failed to read line\");\n# # println!(\"You guessed: {}\", guess);\n} Rustコミュニティに所属する人々が crates.io でたくさんのパッケージを利用できるようにしてくれており、上と同じステップを踏めばそれらをあなたのパッケージに取り込むことができます:あなたのパッケージの Cargo.toml ファイルにそれらを書き並べ、useを使って要素をクレートからスコープへと持ち込めばよいのです。 標準ライブラリ (std) も、私達のパッケージの外部にあるクレートだということに注意してください。 標準ライブラリはRust言語に同梱されているので、 Cargo.toml を stdを含むように変更する必要はありません。 しかし、その要素をそこから私達のパッケージのスコープに持ち込むためには、useを使って参照する必要はあります。 例えば、HashMapには次の行を使います。 use std::collections::HashMap; これは標準ライブラリクレートの名前stdから始まる絶対パスです。","breadcrumbs":"肥大化していくプロジェクトをパッケージ、クレート、モジュールを利用して管理する » 外部のパッケージを使う","id":"121","title":"外部のパッケージを使う"},"122":{"body":"同じクレートか同じモジュールで定義された複数の要素を使おうとする時、それぞれの要素を一行一行並べると、縦に大量のスペースを取ってしまいます。 例えば、Listing 2-4の数当てゲームで使った次の2つのuse文がstdからスコープへ要素を持ち込みました。 ファイル名: src/main.rs # use rand::Rng;\n// --snip--\n// (略)\nuse std::cmp::Ordering;\nuse std::io;\n// --snip--\n// (略)\n# # fn main() {\n# println!(\"Guess the number!\");\n# # let secret_number = rand::thread_rng().gen_range(1, 101);\n# # println!(\"The secret number is: {}\", secret_number);\n# # println!(\"Please input your guess.\");\n# # let mut guess = String::new();\n# # io::stdin()\n# .read_line(&mut guess)\n# .expect(\"Failed to read line\");\n# # println!(\"You guessed: {}\", guess);\n# # match guess.cmp(&secret_number) {\n# Ordering::Less => println!(\"Too small!\"),\n# Ordering::Greater => println!(\"Too big!\"),\n# Ordering::Equal => println!(\"You win!\"),\n# }\n# } 代わりに、ネストしたパスを使うことで、同じ一連の要素を1行でスコープに持ち込めます。 これをするには、Listing 7-18 に示されるように、パスの共通部分を書き、2つのコロンを続け、そこで波括弧で互いに異なる部分のパスのリストを囲みます。 ファイル名: src/main.rs # use rand::Rng;\n// --snip--\n// (略)\nuse std::{cmp::Ordering, io};\n// --snip--\n// (略)\n# # fn main() {\n# println!(\"Guess the number!\");\n# # let secret_number = rand::thread_rng().gen_range(1, 101);\n# # println!(\"The secret number is: {}\", secret_number);\n# # println!(\"Please input your guess.\");\n# # let mut guess = String::new();\n# # io::stdin()\n# .read_line(&mut guess)\n# .expect(\"Failed to read line\");\n# # let guess: u32 = guess.trim().parse().expect(\"Please type a number!\");\n# # println!(\"You guessed: {}\", guess);\n# # match guess.cmp(&secret_number) {\n# Ordering::Less => println!(\"Too small!\"),\n# Ordering::Greater => println!(\"Too big!\"),\n# Ordering::Equal => println!(\"You win!\"),\n# }\n# } Listing 7-18: 同じプレフィックスをもつ複数の要素をスコープに持ち込むためにネストしたパスを指定する 大きなプログラムにおいては、同じクレートやモジュールからのたくさんの要素をネストしたパスで持ち込むようにすれば、独立したuse文の数を大きく減らすことができます! ネストしたパスはパスのどの階層においても使うことができます。これはサブパスを共有する2つのuse文を合体させるときに有用です。 例えば、Listing 7-19 は2つのuse文を示しています:1つはstd::ioをスコープに持ち込み、もう一つはstd::io::Writeをスコープに持ち込んでいます。 ファイル名: src/lib.rs use std::io;\nuse std::io::Write; Listing 7-19: 片方がもう片方のサブパスである2つのuse文 これらの2つのパスの共通部分はstd::ioであり、そしてこれは最初のパスにほかなりません。これらの2つのパスを1つのuse文へと合体させるには、Listing 7-20 に示されるように、ネストしたパスにselfを使いましょう。 ファイル名: src/lib.rs use std::io::{self, Write}; Listing 7-20: Listing 7-19 のパスを一つの use 文に合体させる この行は std::io とstd::io::Write をスコープに持ち込みます。","breadcrumbs":"肥大化していくプロジェクトをパッケージ、クレート、モジュールを利用して管理する » 巨大なuseのリストをネストしたパスを使って整理する","id":"122","title":"巨大なuseのリストをネストしたパスを使って整理する"},"123":{"body":"パスにおいて定義されているすべての公開要素をスコープに持ち込みたいときは、glob演算子 * をそのパスの後ろに続けて書きましょう: use std::collections::*; このuse文はstd::collectionsのすべての公開要素を現在のスコープに持ち込みます。 glob演算子を使う際にはご注意を! globをすると、どの名前がスコープ内にあり、プログラムで使われている名前がどこで定義されたのか分かりづらくなります。 glob演算子はしばしば、テストの際、テストされるあらゆるものをtestsモジュールに持ち込むために使われます。これについては11章 テストの書き方 の節で話します。 glob演算子はプレリュードパターンの一部としても使われることがあります:そのようなパターンについて、より詳しくは 標準ライブラリのドキュメント をご覧ください。","breadcrumbs":"肥大化していくプロジェクトをパッケージ、クレート、モジュールを利用して管理する » glob演算子","id":"123","title":"glob演算子"},"124":{"body":"この章のすべての例において、今までのところ、複数のモジュールを一つのファイルに定義していました。 モジュールが大きくなる時、コードを読み進めやすくするため、それらの定義を別のファイルへ移動させたくなるかもしれません。 例えば、Listing 7-17 のコードからはじめましょう。クレートルートのファイルをListing 7-21 のコードを持つように変更して、front_of_houseモジュールをそれ専用のファイルsrc/front_of_house.rsに動かしましょう。 今回、クレートルートファイルはsrc/lib.rsですが、この手続きはクレートルートファイルがsrc/main.rsであるバイナリクレートでもうまく行きます。 ファイル名: src/lib.rs mod front_of_house; pub use crate::front_of_house::hosting; pub fn eat_at_restaurant() { hosting::add_to_waitlist(); hosting::add_to_waitlist(); hosting::add_to_waitlist();\n} Listing 7-21: front_of_houseモジュールを宣言する。その中身はsrc/front_of_house.rs内にある そして、 Listing 7-22 のように、 src/front_of_house.rs にはfront_of_house モジュールの中身の定義を与えます。 ファイル名: src/front_of_house.rs pub mod hosting { pub fn add_to_waitlist() {}\n} Listing 7-22: src/front_of_house.rs における、front_of_houseモジュール内部の定義 mod front_of_houseの後にブロックではなくセミコロンを使うと、Rustにモジュールの中身をモジュールと同じ名前をした別のファイルから読み込むように命令します。 私達の例で、つづけてhostingモジュールをそれ専用のファイルに抽出するには、src/front_of_house.rsがhostingモジュールの宣言のみを含むように変更します: ファイル名: src/front_of_house.rs pub mod hosting; さらに src/front_of_house ディレクトリと src/front_of_house/hosting.rs ファイルを作って、hostingモジュール内でなされていた定義を持つようにします。 ファイル名: src/front_of_house/hosting.rs pub fn add_to_waitlist() {} 定義は別のファイルにあるにもかかわらず、モジュールツリーは同じままであり、eat_at_restaurant内での関数呼び出しもなんの変更もなくうまく行きます。 このテクニックのおかげで、モジュールが大きくなってきた段階で新しいファイルへ動かす、ということができます。 src/lib.rs におけるpub use crate::front_of_house::hosting という文も変わっていないし、useはどのファイルがクレートの一部としてコンパイルされるかになんの影響も与えないということに注意してください。 modキーワードがモジュールを宣言したなら、Rustはそのモジュールに挿入するためのコードを求めて、モジュールと同じ名前のファイルの中を探すというわけです。","breadcrumbs":"肥大化していくプロジェクトをパッケージ、クレート、モジュールを利用して管理する » モジュールを複数のファイルに分割する","id":"124","title":"モジュールを複数のファイルに分割する"},"125":{"body":"Rustでは、パッケージを複数のクレートに、そしてクレートを複数のモジュールに分割して、あるモジュールで定義された要素を他のモジュールから参照することができます。 これは絶対パスか相対パスを指定することで行なえます。 これらのパスはuse文でスコープに持ち込むことができ、こうすると、そのスコープで要素を複数回使う時に、より短いパスで済むようになります。 モジュールのコードは標準では非公開ですが、pubキーワードを追加することで定義を公開することができます。 次の章では、きちんと整理されたあなたのコードで使うことができる、標準ライブラリのいくつかのコレクションデータ構造を見ていきます。","breadcrumbs":"肥大化していくプロジェクトをパッケージ、クレート、モジュールを利用して管理する » まとめ","id":"125","title":"まとめ"},"126":{"body":"Rustの標準ライブラリは、 コレクション と呼ばれる多くの非常に有益なデータ構造を含んでいます。他の多くのデータ型は、 ある一つの値を表しますが、コレクションは複数の値を含むことができます。組み込みの配列とタプル型とは異なり、 これらのコレクションが指すデータはヒープに確保され、データ量はコンパイル時にわかる必要はなく、 プログラムの実行にあわせて、伸縮可能であることになります。各種のコレクションには異なる能力とコストが存在し、 自分の現在の状況に最適なものを選び取るスキルは、時間とともに育っていきます。この章では、 Rustのプログラムにおいて、非常に頻繁に使用される3つのコレクションについて議論しましょう。 ベクタ型 は、可変長の値を並べて保持できる。 文字列 は、文字のコレクションである。以前、String型について触れたが、 この章ではより掘り下げていく。 ハッシュマップ は、値を特定のキーと紐付けさせてくれる。より一般的なデータ構造である、 マップ の特定の実装である。 標準ライブラリで提供されている他の種のコレクションについて学ぶには、 ドキュメント を参照されたし。 ベクタ型、文字列、ハッシュマップの生成と更新方法や、各々が特別な点について議論していきましょう。","breadcrumbs":"一般的なコレクション","id":"126","title":"一般的なコレクション"},"127":{"body":"最初に見るコレクションは、Vecであり、 ベクタ としても知られています。ベクタは、 メモリ上に値を隣り合わせに並べる単独のデータ構造に2つ以上の値を保持させてくれます。 ベクタには、同じ型の値しか保持できません。要素のリストがある場合に有用です。 例えば、テキストファイルの各行とか、ショッピングカートのアイテムの価格などです。","breadcrumbs":"一般的なコレクション » ベクタで一連の値を保持する","id":"127","title":"ベクタで一連の値を保持する"},"128":{"body":"新しい空のベクタを作るには、リスト8-1に示されたように、Vec::new関数を呼べばよいです。 let v: Vec = Vec::new(); リスト8-1: 新しい空のベクタを生成してi32型の値を保持する ここでは、型注釈を付け足したことに注目してください。このベクタに対して、何も値を挿入していないので、 コンパイラには、どんなデータを保持させるつもりなのかわからないのです。これは重要な点です。ベクタは、 ジェネリクスを使用して実装されているのです; 独自の型でジェネリクスを使用する方法については、 第10章で解説します。今は、標準ライブラリにより提供されているVec型は、どんな型でも保持でき、 特定のベクタが特定の型を保持するとき、その型は山かっこ内に指定されることを知っておいてください。 リスト8-1では、コンパイラにvのVecは、i32型の要素を保持すると指示しました。 より現実的なコードでは、一旦値を挿入したら、コンパイラは保持させたい値の型をしばしば推論できるので、 この型注釈をすることは滅多にありません。初期値のあるVecを生成する方が一般的ですし、 Rustには、利便性のためにvec!というマクロも用意されています。このマクロは、 与えた値を保持する新しいベクタ型を生成します。リスト8-2では、1、2、3という値を持つ新しいVecを生成しています。 let v = vec![1, 2, 3]; リスト8-2: 値を含む新しいベクタを生成する 初期値のi32値を与えたので、コンパイラは、vの型がVecであると推論でき、型注釈は必要なくなりました。 次は、ベクタを変更する方法を見ましょう。","breadcrumbs":"一般的なコレクション » 新しいベクタを生成する","id":"128","title":"新しいベクタを生成する"},"129":{"body":"ベクタを生成し、それから要素を追加するには、リスト8-3に示したように、pushメソッドを使用できます。 let mut v = Vec::new(); v.push(5);\nv.push(6);\nv.push(7);\nv.push(8); リスト8-3: pushメソッドを使用してベクタ型に値を追加する あらゆる変数同様、第3章で議論したように、値を変化させたかったら、mutキーワードで可変にする必要があります。 中に配置する数値は全てi32型であり、コンパイラはこのことをデータから推論するので、 Vecという注釈は必要なくなります。","breadcrumbs":"一般的なコレクション » ベクタを更新する","id":"129","title":"ベクタを更新する"},"13":{"body":"最初の手順は、Rustをインストールすることです。Rustは、rustupというRustのバージョンと関連するツールを管理するコマンドラインツールを使用して、 ダウンロードします。ダウンロードするには、インターネット接続が必要でしょう。 注釈: なんらかの理由でrustupを使用しないことを好むのなら、 Rustインストールページ で、 他の選択肢をご覧になってください。 以下の手順で最新の安定版のRustコンパイラをインストールします。この本の例と出力は全て、安定版のRust1.21.0を使用しています。 Rustの安定性保証により、現在この本の例でコンパイルできるものは、新しいバージョンになってもコンパイルでき続けることを保証します。 出力は、バージョンによって多少異なる可能性があります。Rustは頻繁にエラーメッセージと警告を改善しているからです。 言い換えると、どんな新しいバージョンでもこの手順に従ってインストールした安定版なら、 この本の内容で想定通りに動くはずです。","breadcrumbs":"事始め » インストール","id":"13","title":"インストール"},"130":{"body":"他のあらゆる構造体同様、ベクタもスコープを抜ければ、解放されます。リスト8-4に注釈したようにですね。 { let v = vec![1, 2, 3, 4]; // vで作業をする } // <- vはここでスコープを抜け、解放される リスト8-4: ベクタとその要素がドロップされる箇所を示す ベクタがドロップされると、その中身もドロップされます。つまり、保持されていた整数値が、 片付けられるということです。これは一見単純な点に見えるかもしれませんが、ベクタの要素への参照を導入した途端、 もうちょっと複雑になる可能性を秘めています。次は、それに挑んでいきましょう!","breadcrumbs":"一般的なコレクション » ベクタをドロップすれば、要素もドロップする","id":"130","title":"ベクタをドロップすれば、要素もドロップする"},"131":{"body":"もうベクタを生成し、更新し、破棄する方法を知ったので、中身を読む方法を知るのはいいステップアップです。 ベクタに保持された値を参照する方法は2つあります。例では、さらなる明瞭性を求めて、 これらの関数から返る値の型を注釈しました。 リスト8-5に示したのは、両メソッドがベクタの値に対して、添字記法とgetメソッドによりアクセスするところです。 let v = vec![1, 2, 3, 4, 5]; let third: &i32 = &v[2];\nlet third: Option<&i32> = v.get(2); リスト8-5: 添字記法かgetメソッドを使用してベクタの要素にアクセスする ここでは、2つのことに注目してください。まず、3番目の要素を得るのに2という添え字の値を使用していることです: ベクタは、数値により順序付けされ、添え字は0から始まります。2番目に、3番目の要素を得る2つの方法は、 &と[]を使用して参照を得るものと、番号を引数としてgetメソッドに渡して、Option<&T>を得るものということです。 Rustには要素を参照する方法が2通りあるので、ベクタに要素が含まれない番号の値を使用しようとした時に、 プログラムの振る舞いを選択できます。例として、ベクタに5つ要素があり、添え字100の要素にアクセスを試みた場合、 プログラムがすることを確認しましょう。リスト8-6に示したようにですね。 let v = vec![1, 2, 3, 4, 5]; let does_not_exist = &v[100];\nlet does_not_exist = v.get(100); リスト8-6: 5つの要素を含むベクタの添え字100の要素にアクセスしようとする このコードを走らせると、最初の[]メソッドはプログラムをパニックさせます。存在しない要素を参照しているからです。 このメソッドは、ベクタの終端を超えて要素にアクセスしようとした時にプログラムをクラッシュさせたい場合に最適です。 getメソッドがベクタ外の添え字を渡されると、パニックすることなくNoneを返します。 普通の状態でも、ベクタの範囲外にアクセスする可能性がある場合に、このメソッドを使用することになるでしょう。 そうしたら、コードにはSome(&element)かNoneを扱うロジックが存在することになります。そう、 第6章で議論したように。例えば、添え字は人間に数値を入力してもらうことで得ることもできます。 もし大きすぎる値を誤って入力し、プログラムがNone値を得てしまったら、現在ベクタに幾つ要素があるかをユーザに教え、 再度正しい値を入力してもらうことができるでしょう。その方が、タイプミスでプログラムをクラッシュさせるより、 ユーザに優しくなるでしょう。 プログラムに有効な参照がある場合、借用チェッカー(borrow checker)は(第4章で解説しましたが)、 所有権と借用規則を強制し、ベクタの中身へのこの参照や他のいかなる参照も有効であり続けることを保証してくれます。 同一スコープ上では、可変と不変な参照を同時には存在させられないというルールを思い出してください。 このルールはリスト8-7にも適用され、リスト8-7ではベクタの最初の要素への不変参照を保持し、 終端に要素を追加しようとしていますが、動きません。 let mut v = vec![1, 2, 3, 4, 5]; let first = &v[0]; v.push(6); println!(\"The first element is: {}\", first); リスト8-7: 要素への参照を保持しつつ、ベクタに要素を追加しようとする このコードをコンパイルすると、こんなエラーになります: error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable\n(エラー: 不変としても借用されているので、`v`を可変で借用できません) |\n4 | let first = &v[0]; | - immutable borrow occurs here | (不変借用はここで発生しています)\n5 |\n6 | v.push(6); | ^ mutable borrow occurs here | (可変借用は、ここで発生しています)\n7 | } | - immutable borrow ends here | (不変借用はここで終了しています) リスト8-7のコードは、一見動くはずのように見えるかもしれません: なぜ、最初の要素への参照が、 ベクタの終端への変更を気にかける必要があるのでしょうか?このエラーは、ベクタの動作法のせいです: 新規要素をベクタの終端に追加すると、ベクタが現在存在する位置に隣り合って要素を入れるだけの領域がなかった場合に、 メモリの新規確保をして古い要素を新しいスペースにコピーする必要があるかもしれないからです。 その場合、最初の要素を指す参照は、解放されたメモリを指すことになるでしょう。借用規則により、 そのような場面に陥らないよう回避されるのです。 注釈: Vecの実装に関する詳細については、 “The Rustonomicon” を参照してください。 訳注: 日本語版のThe Rustonomiconは こちら です。","breadcrumbs":"一般的なコレクション » ベクタの要素を読む","id":"131","title":"ベクタの要素を読む"},"132":{"body":"ベクタの要素に順番にアクセスしたいなら、添え字で1回に1要素にアクセスするのではなく、全要素を走査することができます。 リスト8-8でforループを使い、i32のベクタの各要素に対する不変な参照を得て、それらを出力する方法を示しています。 let v = vec![100, 32, 57];\nfor i in &v { println!(\"{}\", i);\n} リスト8-8: forループで要素を走査し、ベクタの各要素を出力する 全要素に変更を加える目的で、可変なベクタの各要素への可変な参照を走査することもできます。 リスト8-9のforループでは、各要素に50を足しています。 let mut v = vec![100, 32, 57];\nfor i in &mut v { *i += 50;\n} リスト8-9: ベクタの要素への可変な参照を走査する 可変参照が参照している値を変更するには、+=演算子を使用する前に、 参照外し演算子(*)を使用してiの値に辿り着かないといけません。","breadcrumbs":"一般的なコレクション » ベクタの値を走査する","id":"132","title":"ベクタの値を走査する"},"133":{"body":"この章の冒頭で、ベクタは同じ型の値しか保持できないと述べました。これは不便なこともあります; 異なる型の要素を保持する必要性が出てくるユースケースも確かにあるわけです。幸運なことに、 enumの列挙子は、同じenumの型の元に定義されるので、ベクタに異なる型の要素を保持する必要が出たら、 enumを定義して使用することができます! 例えば、スプレッドシートの行から値を得たくなったとしましょう。ここで行の列には、整数を含むものや、 浮動小数点数を含むもの、文字列を含むものがあります。列挙子が異なる値の型を保持するenumを定義できます。 そして、このenumの列挙子は全て同じ型: enumの型と考えられるわけです。それからそのenumを保持するベクタを生成でき、 結果的に異なる型を保持できるようになるわけです。リスト8-10でこれを模擬しています。 enum SpreadsheetCell { Int(i32), Float(f64), Text(String),\n} let row = vec![ SpreadsheetCell::Int(3), SpreadsheetCell::Text(String::from(\"blue\")), SpreadsheetCell::Float(10.12),\n]; リスト8-10: enumを定義して、一つのベクタに異なる型の値を保持する 各要素を格納するのにヒープ上でズバリどれくらいのメモリが必要になるかをわかるように、 コンパイラがコンパイル時にベクタに入る型を知る必要があります。副次的な利点は、 このベクタではどんな型が許容されるのか明示できることです。もしRustでベクタがどんな型でも保持できたら、 ベクタの要素に対して行われる処理に対して一つ以上の型がエラーを引き起こす可能性があったでしょう。 enumに加えてmatch式を使うことは、第6章で議論した通り、コンパイル時にありうる場合全てに対処していることをコンパイラが、 保証できることを意味します。 プログラム記述時にプログラムがベクタに実行時に保持するありとあらゆる一連の型をプログラマが知らない場合、 このenumテクニックはうまく動かないでしょう。代わりにトレイトオブジェクトを使用することができ、こちらは第17章で講義します。 今や、ベクタを使用するべき最も一般的な方法について触れ、議論したので、標準ライブラリでVecに定義されている多くの有益なメソッドについては、 APIドキュメントを確認することを心得てください。例として、pushに加えて、popメソッドは最後の要素を削除して返します。 次のコレクション型に移りましょう: Stringです!","breadcrumbs":"一般的なコレクション » Enumを使って複数の型を保持する","id":"133","title":"Enumを使って複数の型を保持する"},"134":{"body":"第4章で文字列について語りましたが、今度はより掘り下げていきましょう。新参者のRustaceanは、 3つの概念の組み合わせにより、文字列でよく行き詰まります: Rustのありうるエラーを晒す性質、 多くのプログラマが思っている以上に文字列が複雑なデータ構造であること、そしてUTF-8です。 これらの要因が、他のプログラミング言語から移ってきた場合、一見困難に見えるように絡み合うわけです。 コレクションの文脈で文字列を議論することは、有用なことです。なぜなら、文字列はテキストとして解釈された時に有用になる機能を提供するメソッドと、 バイトの塊で実装されているからです。この節では、生成、更新、読み込みのような全コレクションが持つStringの処理について語ります。 また、Stringが他のコレクションと異なる点についても議論します。具体的には、人間とコンピュータがStringデータを解釈する方法の差異により、 Stringに添え字アクセスする方法がどう複雑なのかということです。","breadcrumbs":"一般的なコレクション » 文字列でUTF-8でエンコードされたテキストを保持する","id":"134","title":"文字列でUTF-8でエンコードされたテキストを保持する"},"135":{"body":"まずは、 文字列 という用語の意味を定義しましょう。Rustには、言語の核として1種類しか文字列型が存在しません。 文字列スライスのstrで、通常借用された形態&strで見かけます。第4章で、 文字列スライス について語りました。 これは、別の場所に格納されたUTF-8エンコードされた文字列データへの参照です。例えば、文字列リテラルは、 プログラムのバイナリ出力に格納されるので、文字列スライスになります。 String型は、言語の核として組み込まれるのではなく、Rustの標準ライブラリで提供されますが、伸長可能、 可変、所有権のあるUTF-8エンコードされた文字列型です。RustaceanがRustにおいて「文字列」を指したら、 どちらかではなく、Stringと文字列スライスの&strのことを通常意味します。この節は、大方、 Stringについてですが、どちらの型もRustの標準ライブラリで重宝されており、 どちらもUTF-8エンコードされています。 また、Rustの標準ライブラリには、他の文字列型も含まれています。OsString、OsStr、CString、CStrなどです。 ライブラリクレートにより、文字列データを格納する選択肢はさらに増えます。 それらの名前が全てStringかStrで終わっているのがわかりますか?所有権ありと借用されたバージョンを指しているのです。 ちょうど以前見かけたStringと&strのようですね。例えば、これらの文字列型は、異なるエンコード方法でテキストを格納していたり、 メモリ上の表現が異なったりします。この章では、これらの他の種類の文字列については議論しません; 使用方法やどれが最適かについては、APIドキュメントを参照してください。","breadcrumbs":"一般的なコレクション » 文字列とは?","id":"135","title":"文字列とは?"},"136":{"body":"Vecで使用可能な処理の多くがStringでも使用できます。文字列を生成するnew関数から始めましょうか。 リスト8-11に示したようにですね。 let mut s = String::new(); リスト8-11: 新しい空のStringを生成する この行は、新しい空のsという文字列を生成しています。それからここにデータを読み込むことができるわけです。 だいたい、文字列の初期値を決めるデータがあるでしょう。そのために、to_stringメソッドを使用します。 このメソッドは、文字列リテラルのように、Displayトレイトを実装する型ならなんでも使用できます。 リスト8-12に2例、示しています。 let data = \"initial contents\"; let s = data.to_string(); // the method also works on a literal directly:\nlet s = \"initial contents\".to_string(); リスト8-12: to_stringメソッドを使用して文字列リテラルからStringを生成する このコードは、initial contents(初期値)を含む文字列を生成します。 さらに、String::from関数を使っても、文字列リテラルからStringを生成することができます。 リスト8-13のコードは、to_stringを使用するリスト8-12のコードと等価です。 let s = String::from(\"initial contents\"); リスト8-13: String::from関数を使って文字列リテラルからStringを作る 文字列は、非常に多くのものに使用されるので、多くの異なる一般的なAPIを使用でき、たくさんの選択肢があるわけです。 冗長に思われるものもありますが、適材適所です!今回の場合、String::fromとto_stringは全く同じことをします。 従って、どちらを選ぶかは、スタイル次第です。 文字列はUTF-8エンコードされていることを覚えていますか?要するに文字列には、適切にエンコードされていればどんなものでも含めます。 リスト8-14に示したように。 let hello = String::from(\"السلام عليكم\");\nlet hello = String::from(\"Dobrý den\");\nlet hello = String::from(\"Hello\");\nlet hello = String::from(\"שָׁלוֹם\");\nlet hello = String::from(\"नमस्ते\");\nlet hello = String::from(\"こんにちは\");\nlet hello = String::from(\"안녕하세요\");\nlet hello = String::from(\"你好\");\nlet hello = String::from(\"Olá\");\nlet hello = String::from(\"Здравствуйте\");\nlet hello = String::from(\"Hola\"); リスト8-14: いろんな言語の挨拶を文字列に保持する これらは全て、有効なStringの値です。","breadcrumbs":"一般的なコレクション » 新規文字列を生成する","id":"136","title":"新規文字列を生成する"},"137":{"body":"Stringは、サイズを伸ばすことができ、Vecの中身のように、追加のデータをプッシュすれば、中身も変化します。 付け加えると、String値を連結する+演算子や、format!マクロを便利に使用することができます。 push_strとpushで文字列に追加する push_strメソッドで文字列スライスを追記することで、Stringを伸ばすことができます。 リスト8-15の通りです。 let mut s = String::from(\"foo\");\ns.push_str(\"bar\"); リスト8-15: push_strメソッドでStringに文字列スライスを追記する この2行の後、sはfoobarを含むことになります。push_strメソッドは、必ずしも引数の所有権を得なくていいので、 文字列スライスを取ります。例えば、リスト8-16のコードは、中身をs1に追加した後、 s2を使えなかったら残念だということを示しています。 let mut s1 = String::from(\"foo\");\nlet s2 = \"bar\";\ns1.push_str(s2);\nprintln!(\"s2 is {}\", s2); リスト8-16: 中身をStringに追加した後に、文字列スライスを使用する もし、push_strメソッドがs2の所有権を奪っていたら、最後の行でその値を出力することは不可能でしょう。 ところが、このコードは予想通りに動きます! pushメソッドは、1文字を引数として取り、Stringに追加します。リスト8-15は、 pushメソッドで l をStringに追加するコードを呈示しています。 let mut s = String::from(\"lo\");\ns.push('l'); リスト8-17: pushでString値に1文字を追加する このコードの結果、sはlolを含むことになるでしょう。 編者注: lolはlaughing out loud(大笑いする)の頭文字からできたスラングです。 日本語のwwwみたいなものですね。 +演算子、またはformat!マクロで連結 2つのすでにある文字列を組み合わせたくなることがよくあります。リスト8-18に示したように、 一つ目の方法は、+演算子を使用することです。 let s1 = String::from(\"Hello, \");\nlet s2 = String::from(\"world!\");\nlet s3 = s1 + &s2; // s1はムーブされ、もう使用できないことに注意 リスト8-18: +演算子を使用して二つのString値を新しいString値にする このコードの結果、s3という文字列は、Hello, world!を含むことになるでしょう。 追記の後、s1がもう有効でなくなった理由と、s2への参照を使用した理由は、 +演算子を使用した時に呼ばれるメソッドのシグニチャと関係があります。+演算子は、addメソッドを使用し、 そのシグニチャは以下のような感じです: fn add(self, s: &str) -> String { これは、標準ライブラリにあるシグニチャそのものではありません: 標準ライブラリでは、addはジェネリクスで定義されています。 ここでは、ジェネリックな型を具体的な型に置き換えたaddのシグニチャを見ており、これは、 このメソッドをString値とともに呼び出した時に起こることです。ジェネリクスについては、第10章で議論します。 このシグニチャが、+演算子の巧妙な部分を理解するのに必要な手がかりになるのです。 まず、s2には&がついてます。つまり、add関数のs引数のために最初の文字列に2番目の文字列の参照を追加するということです: Stringには&strを追加することしかできません。要するに2つのString値を追加することはできないのです。 でも待ってください。addの第2引数で指定されているように、&s2の型は、&strではなく、 &Stringではないですか。では、なぜ、リスト8-18は、コンパイルできるのでしょうか? add呼び出しで&s2を使える理由は、コンパイラが&String引数を&strに 型強制 してくれるためです。 addメソッド呼び出しの際、コンパイラは、 参照外し型強制 というものを使用し、ここでは、 &s2を&s2[..]に変えるものと考えることができます。参照外し型強制について詳しくは、第15章で議論します。 addがs引数の所有権を奪わないので、この処理後もs2が有効なStringになるわけです。 2番目に、シグニチャからaddはselfの所有権をもらうことがわかります。selfには&がついてい ない からです。 これはつまり、リスト8-18においてs1はadd呼び出しにムーブされ、その後は有効ではなくなるということです。 故に、s3 = s1 + &s2;は両文字列をコピーして新しいものを作るように見えますが、 この文は実際にはs1の所有権を奪い、s2の中身のコピーを追記し、結果の所有権を返すのです。言い換えると、 たくさんのコピーをしているように見えますが、違います; 実装は、コピーよりも効率的です。 複数の文字列を連結する必要が出ると、+演算子の振る舞いは扱いにくくなります: let s1 = String::from(\"tic\");\nlet s2 = String::from(\"tac\");\nlet s3 = String::from(\"toe\"); let s = s1 + \"-\" + &s2 + \"-\" + &s3; ここで、sはtic-tac-toeになるでしょう。+と\"文字のせいで何が起きているのかわかりにくいです。 もっと複雑な文字列の連結には、format!マクロを使用することができます: let s1 = String::from(\"tic\");\nlet s2 = String::from(\"tac\");\nlet s3 = String::from(\"toe\"); let s = format!(\"{}-{}-{}\", s1, s2, s3); このコードでも、sはtic-tac-toeになります。format!マクロは、println!と同様の動作をしますが、 出力をスクリーンに行う代わりに、中身をStringで返すのです。format!を使用したコードの方がはるかに読みやすく、 引数の所有権を奪いません。","breadcrumbs":"一般的なコレクション » 文字列を更新する","id":"137","title":"文字列を更新する"},"138":{"body":"他の多くのプログラミング言語では、文字列中の文字に、添え字で参照してアクセスすることは、有効なコードであり、 一般的な処理です。しかしながら、Rustにおいて、添え字記法でStringの一部にアクセスしようとすると、 エラーが発生するでしょう。リスト8-19の非合法なコードを考えてください。 let s1 = String::from(\"hello\");\nlet h = s1[0]; リスト8-19: 文字列に対して添え字記法を試みる このコードは、以下のようなエラーに落ち着きます: error[E0277]: the trait bound `std::string::String: std::ops::Index<{Integer}>` is not satisfied\n(エラー: トレイト境界`std::string::String: std::ops::Index<{Integer}>`が満たされていません) |>\n3 |> let h = s1[0]; |> ^^^^^ the type `std::string::String` cannot be indexed by `{Integer}` |> (型`std::string::String`は`{Integer}`で添え字アクセスできません) = help: the trait `std::ops::Index<{Integer}>` is not implemented for `std::string::String` (ヘルプ: `std::ops::Index<{Integer}>`というトレイトが`std::string::String`に対して実装されていません) エラーと注釈が全てを物語っています: Rustの文字列は、添え字アクセスをサポートしていないのです。 でも、なぜでしょうか?その疑問に答えるには、Rustがメモリにどのように文字列を保持しているかについて議論する必要があります。 内部表現 StringはVecのラッパです。リスト8-14から適切にUTF-8でエンコードされた文字列の例をご覧ください。 まずは、これ: let len = String::from(\"Hola\").len(); この場合、lenは4になり、これは、文字列\"Hola\"を保持するベクタの長さが4バイトであることを意味します。 これらの各文字は、UTF-8でエンコードすると、1バイトになるのです。しかし、以下の行ではどうでしょうか? (この文字列は大文字のキリル文字Zeで始まり、アラビア数字の3では始まっていないことに注意してください) let len = String::from(\"Здравствуйте\").len(); 文字列の長さはと問われたら、あなたは12と答えるかもしれません。ところが、Rustの答えは、24です: “Здравствуйте”をUTF-8でエンコードすると、この長さになります。各Unicodeスカラー値は、2バイトの領域を取るからです。 それ故に、文字列のバイトの添え字は、必ずしも有効なUnicodeのスカラー値とは相互に関係しないのです。 デモ用に、こんな非合法なRustコードを考えてください: let hello = \"Здравствуйте\";\nlet answer = &hello[0]; answerの値は何になるべきでしょうか?最初の文字のЗになるべきでしょうか?UTF-8エンコードされた時、 Зの最初のバイトは208、2番目は151になるので、answerは実際、208になるべきですが、 208は単独では有効な文字ではありません。この文字列の最初の文字を求めている場合、208を返すことは、 ユーザの望んでいるものではないでしょう; しかしながら、Rustには、バイト添え字0の位置には、そのデータしかないのです。 文字列がラテン文字のみを含む場合でも、ユーザは一般的にバイト値が返ることを望みません: &\"hello\"[0]がバイト値を返す有効なコードだったら、hではなく、104を返すでしょう。 予期しない値を返し、すぐには判明しないバグを引き起こさないために、Rustはこのコードを全くコンパイルせず、 開発過程の早い段階で誤解を防いでくれるのです。 バイトとスカラー値と書記素クラスタ!なんてこった! UTF-8について別の要点は、実際Rustの観点から文字列を見るには3つの関連した方法があるということです: バイトとして、スカラー値として、そして、書記素クラスタ(人間が 文字 と呼ぶものに一番近い)としてです。 ヒンディー語の単語、“नमस्ते”をデーヴァナーガリー(訳注: サンスクリット語とヒンディー語を書くときに使われる書記法)で表記したものを見たら、 以下のような見た目のu8値のベクタとして保持されます: [224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164,\n224, 165, 135] 18バイトになり、このようにしてコンピュータは最終的にこのデータを保持しているわけです。これをUnicodeスカラー値として見たら (Rustのchar型はこれなのですが)このバイトは以下のような見た目になります: ['न', 'म', 'स', '्', 'त', 'े'] ここでは、6つchar値がありますが、4番目と6番目は文字ではありません: 単独では意味をなさないダイアクリティックです。 最後に、書記素クラスタとして見たら、このヒンディー語の単語を作り上げる人間が4文字と呼ぶであろうものが得られます: [\"न\", \"म\", \"स्\", \"ते\"] Rustには、データが表す自然言語に関わらず、各プログラムが必要な解釈方法を選択できるように、 コンピュータが保持する生の文字列データを解釈する方法がいろいろ用意されています。 Rustで文字を得るのにStringに添え字アクセスすることが許されない最後の理由は、 添え字アクセスという処理が常に定数時間(O(1))になると期待されるからです。 しかし、Stringでそのパフォーマンスを保証することはできません。というのも、 合法な文字がいくつあるか決定するのに、最初から添え字まで中身を走査する必要があるからです。","breadcrumbs":"一般的なコレクション » 文字列に添え字アクセスする","id":"138","title":"文字列に添え字アクセスする"},"139":{"body":"文字列に添え字アクセスするのは、しばしば悪い考えです。文字列添え字処理の戻り値の型が明瞭ではないからです: バイト値、文字、書記素クラスタ、あるいは文字列スライスにもなります。故に、文字列スライスを生成するのに、 添え字を使う必要が本当に出た場合にコンパイラは、もっと特定するよう求めてきます。添え字アクセスを特定し、 文字列スライスが欲しいと示唆するためには、[]で1つの数値により添え字アクセスするのではなく、 範囲とともに[]を使って、特定のバイトを含む文字列スライスを作ることができます: let hello = \"Здравствуйте\"; let s = &hello[0..4]; ここで、sは文字列の最初の4バイトを含む&strになります。先ほど、これらの文字は各々2バイトになると指摘しましたから、 sはЗдになります。 &hello[0..1]と使用したら、何が起きるでしょうか?答え: Rustはベクタの非合法な添え字にアクセスしたかのように、 実行時にパニックするでしょう: thread 'main' panicked at 'byte index 1 is not a char boundary; it is inside 'З' (bytes 0..2) of `Здравствуйте`', src/libcore/str/mod.rs:2188:4\n('main'スレッドは「バイト添え字1は文字の境界ではありません; `Здравствуйте`の'З'(バイト番号0から2)の中です」でパニックしました) 範囲を使用して文字列スライスを作る際にはプログラムをクラッシュさせることがあるので、気をつけるべきです。","breadcrumbs":"一般的なコレクション » 文字列をスライスする","id":"139","title":"文字列をスライスする"},"14":{"body":"この章及び、本を通して、端末で使用するなんらかのコマンドを示すことがあります。読者が入力するべき行は、 全て$で始まります。$文字を入れる必要はありません; 各コマンドの開始を示しているだけです。 $で始まらない行は、典型的には直前のコマンドの出力を示します。また、PowerShell限定の例は、 $ではなく、>を使用します。","breadcrumbs":"事始め » コマンドライン表記","id":"14","title":"コマンドライン表記"},"140":{"body":"幸いなことに、他の方法でも文字列の要素にアクセスすることができます。 もし、個々のUnicodeスカラー値に対して処理を行う必要があったら、最適な方法はcharsメソッドを使用するものです。 “नमस्ते”に対してcharsを呼び出したら、分解して6つのchar型の値を返すので、各要素にアクセスするには、 その結果を走査すればいいわけです: for c in \"नमस्ते\".chars() { println!(\"{}\", c);\n} このコードは、以下のように出力します: न\nम\nस\n्\nत\nे bytesメソッドは、各バイトをそのまま返すので、最適になることもあるかもしれません: for b in \"नमस्ते\".bytes() { println!(\"{}\", b);\n} このコードは、Stringをなす18バイトを出力します: 224\n164\n// --snip--\n165\n135 ですが、合法なUnicodeスカラー値は、2バイト以上からなる場合もあることは心得ておいてください。 書記素クラスタを文字列から得る方法は複雑なので、この機能は標準ライブラリでは提供されていません。 この機能が必要なら、 crates.io でクレートを入手可能です。","breadcrumbs":"一般的なコレクション » 文字列を走査するメソッド群","id":"140","title":"文字列を走査するメソッド群"},"141":{"body":"まとめると、文字列は込み入っています。プログラミング言語ごとにこの複雑性をプログラマに提示する方法は違います。 Rustでは、Stringデータを正しく扱うことが、全てのRustプログラムにとっての既定動作になっているわけであり、 これは、プログラマがUTF-8データを素直に扱う際に、よりしっかり考えないといけないことを意味します。 このトレードオフにより、他のプログラミング言語で見えるよりも文字列の複雑性がより露出していますが、 ASCII以外の文字に関するエラーを開発の後半で扱わなければならない可能性が排除されているのです。 もう少し複雑でないものに切り替えていきましょう: ハッシュマップです!","breadcrumbs":"一般的なコレクション » 文字列はそう単純じゃない","id":"141","title":"文字列はそう単純じゃない"},"142":{"body":"一般的なコレクションのトリを飾るのは、 ハッシュマップ です。型HashMapは、 K型のキーとV型の値の対応関係を保持します。これを ハッシュ関数 を介して行います。 ハッシュ関数は、キーと値のメモリ配置方法を決めるものです。多くのプログラミング言語でもこの種のデータ構造はサポートされていますが、 しばしば名前が違います。hash、map、object、ハッシュテーブル、連想配列など、枚挙に暇(いとま)はありません。 ハッシュマップは、ベクタのように番号ではなく、どんな型にもなりうるキーを使ってデータを参照したいときに有用です。 例えば、ゲームにおいて、各チームのスコアをハッシュマップで追いかけることができます。ここで、各キーはチーム名、 値が各チームのスコアになります。チーム名が与えられれば、スコアを扱うことができるわけです。 この節でハッシュマップの基礎的なAPIを見ていきますが、より多くのグッズが標準ライブラリにより、 HashMap上に定義された関数に隠されています。いつものように、 もっと情報が欲しければ、標準ライブラリのドキュメントをチェックしてください。","breadcrumbs":"一般的なコレクション » キーとそれに紐づいた値をハッシュマップに格納する","id":"142","title":"キーとそれに紐づいた値をハッシュマップに格納する"},"143":{"body":"空のハッシュマップをnewで作り、要素をinsertで追加することができます。リスト8-20では、 名前がブルーとイエローの2チームのスコアを追いかけています。ブルーチームは10点から、イエローチームは50点から始まります。 use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from(\"Blue\"), 10);\nscores.insert(String::from(\"Yellow\"), 50); リスト8-20: ハッシュマップを生成してキーと値を挿入する 最初に標準ライブラリのコレクション部分からHashMapをuseする必要があることに注意してください。 今までの3つの一般的なコレクションの内、これが最も使用頻度が低いので、初期化処理で自動的にスコープに導入される機能には含まれていません。 また、標準ライブラリからのサポートもハッシュマップは少ないです; 例えば、生成するための組み込みマクロがありません。 ベクタと全く同様に、ハッシュマップはデータをヒープに保持します。このHashMapはキーがString型、 値はi32型です。ベクタのように、ハッシュマップは均質です: キーは全て同じ型でなければならず、 値も全て同じ型でなければなりません。 ハッシュマップを生成する別の方法は、タプルのベクタに対してcollectメソッドを使用するものです。 ここで、各タプルは、キーと値から構成されています。collectメソッドはいろんなコレクション型にデータをまとめ上げ、 そこにはHashMapも含まれています。例として、チーム名と初期スコアが別々のベクタに含まれていたら、 zipメソッドを使ってタプルのベクタを作り上げることができ、そこでは「ブルー」は10とペアになるなどします。 リスト8-21に示したように、それからcollectメソッドを使って、そのタプルのベクタをハッシュマップに変換することができるわけです。 use std::collections::HashMap; let teams = vec![String::from(\"Blue\"), String::from(\"Yellow\")];\nlet initial_scores = vec![10, 50]; let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect(); リスト8-21: チームのリストとスコアのリストからハッシュマップを作る ここでは、HashMap<_, _>という型注釈が必要になります。なぜなら、いろんなデータ構造にまとめ上げることができ、 コンパイラは指定しない限り、どれを所望なのかわからないからです。ところが、キーと値の型引数については、 アンダースコアを使用しており、コンパイラはベクタのデータ型に基づいてハッシュマップが含む型を推論することができるのです。","breadcrumbs":"一般的なコレクション » 新規ハッシュマップを生成する","id":"143","title":"新規ハッシュマップを生成する"},"144":{"body":"i32のようなCopyトレイトを実装する型について、値はハッシュマップにコピーされます。 Stringのような所有権のある値なら、値はムーブされ、リスト8-22でデモされているように、 ハッシュマップはそれらの値の所有者になるでしょう。 use std::collections::HashMap; let field_name = String::from(\"Favorite color\");\nlet field_value = String::from(\"Blue\"); let mut map = HashMap::new();\nmap.insert(field_name, field_value);\n// field_name and field_value are invalid at this point, try using them and\n// see what compiler error you get!\n// field_nameとfield_valueはこの時点で無効になる。試しに使ってみて\n// どんなコンパイルエラーが出るか確認してみて! リスト8-22: 一旦挿入されたら、キーと値はハッシュマップに所有されることを示す insertを呼び出してfield_nameとfield_valueがハッシュマップにムーブされた後は、 これらの変数を使用することは叶いません。 値への参照をハッシュマップに挿入したら、値はハッシュマップにムーブされません。参照が指している値は、 最低でもハッシュマップが有効な間は、有効でなければなりません。これらの問題について詳細には、 第10章の「ライフタイムで参照を有効化する」節で語ります。","breadcrumbs":"一般的なコレクション » ハッシュマップと所有権","id":"144","title":"ハッシュマップと所有権"},"145":{"body":"リスト8-23に示したように、キーをgetメソッドに提供することで、ハッシュマップから値を取り出すことができます。 use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from(\"Blue\"), 10);\nscores.insert(String::from(\"Yellow\"), 50); let team_name = String::from(\"Blue\");\nlet score = scores.get(&team_name); リスト8-23: ハッシュマップに保持されたブルーチームのスコアにアクセスする ここで、scoreはブルーチームに紐づけられた値になり、結果はSome(&10)となるでしょう。 結果はSomeに包まれます。というのも、getはOption<&V>を返すからです; キーに対応する値がハッシュマップになかったら、 getはNoneを返すでしょう。プログラムは、このOptionを第6章で講義した方法のどれかで扱う必要があるでしょう。 ベクタのように、forループでハッシュマップのキーと値のペアを走査することができます: use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from(\"Blue\"), 10);\nscores.insert(String::from(\"Yellow\"), 50); for (key, value) in &scores { println!(\"{}: {}\", key, value);\n} このコードは、各ペアを任意の順番で出力します: Yellow: 50\nBlue: 10","breadcrumbs":"一般的なコレクション » ハッシュマップの値にアクセスする","id":"145","title":"ハッシュマップの値にアクセスする"},"146":{"body":"キーと値の数は伸長可能なものの、各キーには1回に1つの値しか紐づけることができません。 ハッシュマップ内のデータを変えたい時は、すでにキーに値が紐づいている場合の扱い方を決めなければなりません。 古い値を新しい値で置き換えて、古い値を完全に無視することもできます。古い値を保持して、 新しい値を無視し、キーにまだ値が ない 場合に新しい値を追加するだけにすることもできます。 あるいは、古い値と新しい値を組み合わせることもできます。各方法について見ていきましょう! 値を上書きする キーと値をハッシュマップに挿入し、同じキーを異なる値で挿入したら、そのキーに紐づけられている値は置換されます。 リスト8-24のコードは、insertを二度呼んでいるものの、ハッシュマップには一つのキーと値の組しか含まれません。 なぜなら、ブルーチームキーに対する値を2回とも挿入しているからです。 use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from(\"Blue\"), 10);\nscores.insert(String::from(\"Blue\"), 25); println!(\"{:?}\", scores); リスト8-24: 特定のキーで保持された値を置き換える このコードは、{\"Blue\": 25}と出力するでしょう。10という元の値は上書きされたのです。 キーに値がなかった時のみ値を挿入する 特定のキーに値があるか確認することは一般的であり、存在しない時に値を挿入することも一般的です。 ハッシュマップには、これを行うentryと呼ばれる特別なAPIがあり、これは、引数としてチェックしたいキーを取ります。 このentryメソッドの戻り値は、Entryと呼ばれるenumであり、これは存在したりしなかったりする可能性のある値を表します。 イエローチームに対するキーに値が紐づけられているか否か確認したくなったとしましょう。存在しなかったら、 50という値を挿入したく、ブルーチームに対しても同様です。entryAPIを使用すれば、コードはリスト8-25のようになります。 use std::collections::HashMap; let mut scores = HashMap::new();\nscores.insert(String::from(\"Blue\"), 10); scores.entry(String::from(\"Yellow\")).or_insert(50);\nscores.entry(String::from(\"Blue\")).or_insert(50); println!(\"{:?}\", scores); リスト8-25: entryメソッドを使ってキーに値がない場合だけ挿入する Entry上のor_insertメソッドは、対応するEntryキーが存在した時にそのキーに対する値への可変参照を返すために定義されており、 もしなかったら、引数をこのキーの新しい値として挿入し、新しい値への可変参照を返します。このテクニックの方が、 そのロジックを自分で書くよりもはるかに綺麗な上に、borrow checkerとも親和性が高くなります。 リスト8-25のコードを実行すると、{\"Yellow\": 50, \"Blue\": 10}と出力するでしょう。 最初のentry呼び出しは、まだイエローチームに対する値がないので、値50でイエローチームのキーを挿入します。 entryの2回目の呼び出しはハッシュマップを変更しません。なぜなら、ブルーチームには既に10という値があるからです。 古い値に基づいて値を更新する ハッシュマップの別の一般的なユースケースは、キーの値を探し、古い値に基づいてそれを更新することです。 例えば、リスト8-26は、各単語があるテキストに何回出現するかを数え上げるコードを示しています。 キーに単語を入れたハッシュマップを使用し、その単語を何回見かけたか追いかけるために値を増やします。 ある単語を見かけたのが最初だったら、まず0という値を挿入します: use std::collections::HashMap; let text = \"hello world wonderful world\"; let mut map = HashMap::new(); for word in text.split_whitespace() { let count = map.entry(word).or_insert(0); *count += 1;\n} println!(\"{:?}\", map); リスト8-26: 単語とカウントを保持するハッシュマップを使って単語の出現数をカウントする このコードは、{\"world\": 2, \"hello\": 1, \"wonderful\": 1}と出力するでしょう。 or_insert関数は実際、このキーに対する値への可変参照(&mut V)を返すのです。 ここでその可変参照をcount変数に保持しているので、その値に代入するには、 まずアスタリスク(*)でcountを参照外ししなければならないのです。この可変参照は、 forループの終端でスコープを抜けるので、これらの変更は全て安全であり、借用規則により許可されるのです。","breadcrumbs":"一般的なコレクション » ハッシュマップを更新する","id":"146","title":"ハッシュマップを更新する"},"147":{"body":"標準では、HashMapはサービス拒否(DoS)アタックに対して抵抗を示す暗号学的に安全なハッシュ関数を使用します。 これは、利用可能な最速のハッシュアルゴリズムではありませんが、パフォーマンスの欠落と引き換えに安全性を得るというトレードオフは、 価値があります。自分のコードをプロファイリングして、自分の目的では標準のハッシュ関数は遅すぎると判明したら、 異なる hasher を指定することで別の関数に切り替えることができます。hasherとは、 BuildHasherトレイトを実装する型のことです。トレイトについてとその実装方法については、第10章で語ります。 必ずしも独自のhasherを1から作り上げる必要はありません; crates.io には、 他のRustユーザによって共有された多くの一般的なハッシュアルゴリズムを実装したhasherを提供するライブラリがあります。","breadcrumbs":"一般的なコレクション » ハッシュ関数","id":"147","title":"ハッシュ関数"},"148":{"body":"ベクタ、文字列、ハッシュマップはデータを保持し、アクセスし、変更する必要のあるプログラムで必要になる、 多くの機能を提供してくれるでしょう。今なら解決可能なはずの練習問題を用意しました: 整数のリストが与えられ、ベクタを使ってmean(平均値)、median(ソートされた時に真ん中に来る値)、 mode(最も頻繁に出現する値; ハッシュマップがここでは有効活用できるでしょう)を返してください。 文字列をピッグ・ラテン(訳注: 英語の言葉遊びの一つ)に変換してください。各単語の最初の子音は、 単語の終端に移り、\"ay\"が足されます。従って、\"first\"は\"irst-fay\"になります。ただし、 母音で始まる単語には、お尻に\"hay\"が付け足されます(\"apple\"は\"apple-hay\"になります)。 UTF-8エンコードに関する詳細を心に留めておいてください! ハッシュマップとベクタを使用して、ユーザに会社の部署に雇用者の名前を追加させられるテキストインターフェイスを作ってください。 例えば、\"Add Sally to Engineering\"(開発部門にサリーを追加)や\"Add Amir to Sales\"(販売部門にアミールを追加)などです。 それからユーザに、ある部署にいる人間の一覧や部署ごとにアルファベット順で並べ替えられた会社の全人間の一覧を扱わせてあげてください。 標準ライブラリのAPIドキュメントには、この練習問題に有用な、ベクタ、文字列、ハッシュマップのメソッドが解説されています。 処理が失敗することもあるような、より複雑なプログラムに入り込んできています; ということは、 エラーの処理法について議論するのにぴったりということです。次はそれをします!","breadcrumbs":"一般的なコレクション » まとめ","id":"148","title":"まとめ"},"149":{"body":"Rustの信頼性への傾倒は、エラー処理にも及びます。ソフトウェアにおいて、エラーは生きている証しです。 従って、Rustには何かがおかしくなる場面を扱う機能がたくさんあります。多くの場面で、 コンパイラは、プログラマにエラーの可能性を知り、コードのコンパイルが通るまでに何かしら対応を行うことを要求してきます。 この要求により、エラーを発見し、コードを実用に供する前に適切に対処していることを確認することでプログラムを頑健なものにしてくれるのです! Rustでは、エラーは大きく二つに分類されます: 回復可能 と 回復不能 なエラーです。 ファイルが見つからないなどの回復可能なエラーには、問題をユーザに報告し、処理を再試行することが合理的になります。 回復不能なエラーは、常にバグの兆候です。例えば、配列の境界を超えた箇所にアクセスしようとすることなどです。 多くの言語では、この2種のエラーを区別することはなく、例外などの機構を使用して同様に扱います。 Rustには例外が存在しません。代わりに、回復可能なエラーにはResult値があり、 プログラムが回復不能なエラーに遭遇した時には、実行を中止するpanic!マクロがあります。 この章では、まずpanic!の呼び出しを講義し、それからResultを戻り値にする話をします。 加えて、エラーからの回復を試みるか、実行を中止するか決定する際に考慮すべき事項についても、探究しましょう。","breadcrumbs":"エラー処理","id":"149","title":"エラー処理"},"15":{"body":"LinuxかmacOSを使用しているなら、端末を開き、以下のコマンドを入力してください: $ curl https://sh.rustup.rs -sSf | sh このコマンドはスクリプトをダウンロードし、rustupツールのインストールを開始し、Rustの最新の安定版をインストールします。 パスワードを求められる可能性があります。インストールがうまく行けば、以下の行が出現するでしょう: Rust is installed now. Great! お好みでご自由にスクリプトをダウンロードし、実行前に調査することもできます。 インストールスクリプトは、次回のログイン後にRustをシステムのPATHに自動的に追加します。端末を再起動するのではなく、 いますぐにRustを使用し始めたいのなら、シェルで以下のコマンドを実行してRustをシステムのPATHに手動で追加します: $ source $HOME/.cargo/env また、以下の行を ~/.bash_profile に追加することもできます: $ export PATH=\"$HOME/.cargo/bin:$PATH\" さらに、なんらかの類のリンカが必要になるでしょう。既にインストールされている可能性が高いものの、 Rustプログラムのコンパイルを試みて、リンカが実行できないというエラーが出たら、 システムにリンカがインストールされていないということなので、手動でインストールする必要があるでしょう。 Cコンパイラは通常正しいリンカとセットになっています。 自分のプラットフォームのドキュメンテーションを見てCコンパイラのインストール方法を確認してください。 一般的なRustパッケージの中には、Cコードに依存し、Cコンパイラが必要になるものもあります。 故に今インストールする価値はあるかもしれません。","breadcrumbs":"事始め » LinuxとmacOSにrustupをインストールする","id":"15","title":"LinuxとmacOSにrustupをインストールする"},"150":{"body":"時として、コードで悪いことが起きるものです。そして、それに対してできることは何もありません。 このような場面で、Rustにはpanic!マクロが用意されています。panic!マクロが実行されると、 プログラムは失敗のメッセージを表示し、スタックを巻き戻し掃除して、終了します。これが最もありふれて起こるのは、 何らかのバグが検出された時であり、プログラマには、どうエラーを処理すればいいか明確ではありません。","breadcrumbs":"エラー処理 » panic!で回復不能なエラー","id":"150","title":"panic!で回復不能なエラー"},"151":{"body":"標準では、パニックが発生すると、プログラムは 巻き戻し を始めます。つまり、言語がスタックを遡り、 遭遇した各関数のデータを片付けるということです。しかし、この遡りと片付けはすべきことが多くなります。 対立案は、即座に異常終了し、片付けをせずにプログラムを終了させることです。そうなると、プログラムが使用していたメモリは、 OSが片付ける必要があります。プロジェクトにおいて、実行可能ファイルを極力小さくする必要があれば、 Cargo.toml ファイルの適切な[profile]欄にpanic = 'abort'を追記することで、 パニック時に巻き戻しから異常終了するように切り替えることができます。例として、 リリースモード時に異常終了するようにしたければ、以下を追記してください: [profile.release]\npanic = 'abort' 単純なプログラムでpanic!の呼び出しを試してみましょう: ファイル名: src/main.rs fn main() { panic!(\"crash and burn\"); //クラッシュして炎上\n} このプログラムを実行すると、以下のような出力を目の当たりにするでしょう: $ cargo run Compiling panic v0.1.0 (file:///projects/panic) Finished dev [unoptimized + debuginfo] target(s) in 0.25 secs Running `target/debug/panic`\nthread 'main' panicked at 'crash and burn', src/main.rs:2:4\n('main'スレッドはsrc/main.rs:2:4の「クラッシュして炎上」でパニックしました)\nnote: Run with `RUST_BACKTRACE=1` for a backtrace. panic!の呼び出しが、最後の2行に含まれるエラーメッセージを発生させているのです。 1行目にパニックメッセージとソースコード中でパニックが発生した箇所を示唆しています: src/main.rs:2:4 は、 src/main.rs ファイルの2行目4文字目であることを示しています。 この場合、示唆される行は、自分のコードの一部で、その箇所を見に行けば、panic!マクロ呼び出しがあるわけです。 それ以外では、panic!呼び出しが、自分のコードが呼び出しているコードの一部になっている可能性もあるわけです。 エラーメッセージで報告されるファイル名と行番号が、結果的にpanic!呼び出しに導いた自分のコードの行ではなく、 panic!マクロが呼び出されている他人のコードになるでしょう。panic!呼び出しの発生元である関数のバックトレースを使用して、 問題を起こしている自分のコードの箇所を割り出すことができます。バックトレースがどんなものか、次に議論しましょう。","breadcrumbs":"エラー処理 » パニックに対してスタックを巻き戻すか異常終了するか","id":"151","title":"パニックに対してスタックを巻き戻すか異常終了するか"},"152":{"body":"別の例を眺めて、自分のコードでマクロを直接呼び出す代わりに、コードに存在するバグにより、 ライブラリでpanic!呼び出しが発生するとどんな感じなのか確かめてみましょう。リスト9-1は、 添え字でベクタの要素にアクセスを試みる何らかのコードです。 ファイル名: src/main.rs fn main() { let v = vec![1, 2, 3]; v[99];\n} リスト9-1: ベクタの境界を超えて要素へのアクセスを試み、panic!の呼び出しを発生させる ここでは、ベクタの100番目の要素(添え字は0始まりなので添え字99)にアクセスを試みていますが、ベクタには3つしか要素がありません。 この場面では、Rustはパニックします。[]の使用は、要素を返すと想定されるものの、 無効な添え字を渡せば、ここでRustが返せて正しいと思われる要素は何もないわけです。 他の言語(Cなど)では、この場面で欲しいものではないにもかかわらず、まさしく要求したものを返そうとしてきます: メモリがベクタに属していないにもかかわらず、ベクタ内のその要素に対応するメモリ上の箇所にあるものを何か返してくるのです。 これは、 バッファー外読み出し (buffer overread; 訳注: バッファー読みすぎとも解釈できるか)と呼ばれ、 攻撃者が、配列の後に格納された読めるべきでないデータを読み出せるように添え字を操作できたら、 セキュリティ脆弱性につながる可能性があります。 この種の脆弱性からプログラムを保護するために、存在しない添え字の要素を読もうとしたら、 Rustは実行を中止し、継続を拒みます。試して確認してみましょう: $ cargo run Compiling panic v0.1.0 (file:///projects/panic) Finished dev [unoptimized + debuginfo] target(s) in 0.27 secs Running `target/debug/panic`\nthread 'main' panicked at 'index out of bounds: the len is 3 but the index is\n99', /checkout/src/liballoc/vec.rs:1555:10\n('main'スレッドは、/checkout/src/liballoc/vec.rs:1555:10の\n「境界外番号: 長さは3なのに、添え字は99です」でパニックしました)\nnote: Run with `RUST_BACKTRACE=1` for a backtrace. このエラーは、自分のファイルではない vec.rs ファイルを指しています。 標準ライブラリのVecの実装です。ベクタvに対して[]を使った時に走るコードは、 vec.rs に存在し、ここで実際にpanic!が発生しているのです。 その次の注釈行は、RUST_BACKTRACE環境変数をセットして、まさしく何が起き、 エラーが発生したのかのバックトレースを得られることを教えてくれています。 バックトレース とは、ここに至るまでに呼び出された全関数の一覧です。Rustのバックトレースも、 他の言語同様に動作します: バックトレースを読むコツは、頭からスタートして自分のファイルを見つけるまで読むことです。 そこが、問題の根源になるのです。自分のファイルを言及している箇所以前は、自分のコードで呼び出したコードになります; 以後は、自分のコードを呼び出しているコードになります。これらの行には、Rustの核となるコード、標準ライブラリのコード、 使用しているクレートなどが含まれるかもしれません。RUST_BACKTRACE環境変数を0以外の値にセットして、 バックトレースを出力してみましょう。リスト9-2のような出力が得られるでしょう。 $ RUST_BACKTRACE=1 cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs Running `target/debug/panic`\nthread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', /checkout/src/liballoc/vec.rs:1555:10\nstack backtrace: 0: std::sys::imp::backtrace::tracing::imp::unwind_backtrace at /checkout/src/libstd/sys/unix/backtrace/tracing/gcc_s.rs:49 1: std::sys_common::backtrace::_print at /checkout/src/libstd/sys_common/backtrace.rs:71 2: std::panicking::default_hook::{{closure}} at /checkout/src/libstd/sys_common/backtrace.rs:60 at /checkout/src/libstd/panicking.rs:381 3: std::panicking::default_hook at /checkout/src/libstd/panicking.rs:397 4: std::panicking::rust_panic_with_hook at /checkout/src/libstd/panicking.rs:611 5: std::panicking::begin_panic at /checkout/src/libstd/panicking.rs:572 6: std::panicking::begin_panic_fmt at /checkout/src/libstd/panicking.rs:522 7: rust_begin_unwind at /checkout/src/libstd/panicking.rs:498 8: core::panicking::panic_fmt at /checkout/src/libcore/panicking.rs:71 9: core::panicking::panic_bounds_check at /checkout/src/libcore/panicking.rs:58 10: as core::ops::index::Index>::index at /checkout/src/liballoc/vec.rs:1555 11: panic::main at src/main.rs:4 12: __rust_maybe_catch_panic at /checkout/src/libpanic_unwind/lib.rs:99 13: std::rt::lang_start at /checkout/src/libstd/panicking.rs:459 at /checkout/src/libstd/panic.rs:361 at /checkout/src/libstd/rt.rs:61 14: main 15: __libc_start_main 16: リスト9-2: RUST_BACKTRACE環境変数をセットした時に表示される、 panic!呼び出しが生成するバックトレース 出力が多いですね!OSやRustのバージョンによって、出力の詳細は変わる可能性があります。この情報とともに、 バックトレースを得るには、デバッグシンボルを有効にしなければなりません。デバッグシンボルは、 --releaseオプションなしでcargo buildやcargo runを使用していれば、標準で有効になり、 ここではそうなっています。 リスト9-2の出力で、バックトレースの11行目が問題発生箇所を指し示しています: src/main.rs の4行目です。 プログラムにパニックしてほしくなければ、自分のファイルについて言及している最初の行で示されている箇所が、 どのようにパニックを引き起こす値でこの箇所にたどり着いたか割り出すために調査を開始すべき箇所になります。 バックトレースの使用法を模擬するためにわざとパニックするコードを書いたリスト9-1において、 パニックを解消する方法は、3つしか要素のないベクタの添え字99の要素を要求しないことです。 将来コードがパニックしたら、パニックを引き起こすどんな値でコードがどんな動作をしているのかと、 代わりにコードは何をすべきなのかを算出する必要があるでしょう。 この章の後ほど、「panic!するかpanic!するまいか」節でpanic!とエラー状態を扱うのにpanic!を使うべき時と使わぬべき時に戻ってきます。 次は、Resultを使用してエラーから回復する方法を見ましょう。","breadcrumbs":"エラー処理 » panic!バックトレースを使用する","id":"152","title":"panic!バックトレースを使用する"},"153":{"body":"多くのエラーは、プログラムを完全にストップさせるほど深刻ではありません。時々、関数が失敗した時に、 容易に解釈し、対応できる理由によることがあります。例えば、ファイルを開こうとして、 ファイルが存在しないために処理が失敗したら、プロセスを停止するのではなく、ファイルを作成したいことがあります。 第2章の 「Result型で失敗する可能性に対処する」 でResult enumが以下のように、 OkとErrの2列挙子からなるよう定義されていることを思い出してください: enum Result { Ok(T), Err(E),\n} TとEは、ジェネリックな型引数です: ジェネリクスについて詳しくは、第10章で議論します。 たった今知っておく必要があることは、Tが成功した時にOk列挙子に含まれて返される値の型を表すことと、 Eが失敗した時にErr列挙子に含まれて返されるエラーの型を表すことです。Resultはこのようなジェネリックな型引数を含むので、 標準ライブラリ上に定義されているResult型や関数などを、成功した時とエラーの時に返したい値が異なるような様々な場面で使用できるのです。 関数が失敗する可能性があるためにResult値を返す関数を呼び出しましょう: リスト9-3では、 ファイルを開こうとしています。 ファイル名: src/main.rs use std::fs::File; fn main() { let f = File::open(\"hello.txt\");\n} リスト9-3: ファイルを開く File::openがResultを返すとどう知るのでしょうか?標準ライブラリのAPIドキュメントを参照することもできますし、 コンパイラに尋ねることもできます!fに関数の戻り値では ない と判明している型注釈を与えて、 コードのコンパイルを試みれば、コンパイラは型が合わないと教えてくれるでしょう。そして、エラーメッセージは、 fの 実際の 型を教えてくれるでしょう。試してみましょう!File::openの戻り値の型はu32ではないと判明しているので、 let f文を以下のように変更しましょう: let f: u32 = File::open(\"hello.txt\"); これでコンパイルしようとすると、以下のような出力が得られます: error[E0308]: mismatched types\n(エラー: 型が合いません) --> src/main.rs:4:18 |\n4 | let f: u32 = File::open(\"hello.txt\"); | ^^^^^^^^^^^^^^^^^^^^^^^ expected u32, found enum\n`std::result::Result` | = note: expected type `u32` (注釈: 予期した型は`u32`です) found type `std::result::Result` (実際の型は`std::result::Result`です) これにより、File::open関数の戻り値の型は、Resultであることがわかります。ジェネリック引数のTは、 ここでは成功値の型std::fs::Fileで埋められていて、これはファイルハンドルです。 エラー値で使用されているEの型は、std::io::Errorです。 この戻り値型は、File::openの呼び出しが成功し、読み込みと書き込みを行えるファイルハンドルを返す可能性があることを意味します。 また、関数呼び出しは失敗もする可能性があります: 例えば、ファイルが存在しない可能性、ファイルへのアクセス権限がない可能性です。 File::openには成功したか失敗したかを知らせる方法とファイルハンドルまたは、エラー情報を与える方法が必要なのです。 この情報こそがResult enumが伝達するものなのです。 File::openが成功した場合、変数fの値はファイルハンドルを含むOkインスタンスになります。 失敗した場合には、発生したエラーの種類に関する情報をより多く含むErrインスタンスがfの値になります。 リスト9-3のコードに追記をしてFile::openが返す値に応じて異なる動作をする必要があります。 リスト9-4に基礎的な道具を使ってResultを扱う方法を一つ示しています。第6章で議論したmatch式です。 ファイル名: src/main.rs use std::fs::File; fn main() { let f = File::open(\"hello.txt\"); let f = match f { Ok(file) => file, Err(error) => { // ファイルを開く際に問題がありました panic!(\"There was a problem opening the file: {:?}\", error) }, };\n} リスト9-4: match式を使用して返却される可能性のあるResult列挙子を処理する Option enumのように、Result enumとその列挙子は、初期化処理でインポートされているので、 matchアーム内でOkとErr列挙子の前にResult::を指定する必要がないことに注目してください。 ここでは、結果がOkの時に、Ok列挙子から中身のfile値を返すように指示し、 それからそのファイルハンドル値を変数fに代入しています。matchの後には、 ファイルハンドルを使用して読み込んだり書き込むことができるわけです。 matchのもう一つのアームは、File::openからErr値が得られたケースを処理しています。 この例では、panic!マクロを呼び出すことを選択しています。カレントディレクトリに hello.txt というファイルがなく、 このコードを走らせたら、panic!マクロからの以下のような出力を目の当たりにするでしょう: thread 'main' panicked at 'There was a problem opening the file: Error { repr:\nOs { code: 2, message: \"No such file or directory\" } }', src/main.rs:9:12\n('main'スレッドは、src/main.rs:9:12の「ファイルを開く際に問題がありました: Error{ repr:\nOs { code: 2, message: \"そのような名前のファイルまたはディレクトリはありません\"}}」でパニックしました) 通常通り、この出力は、一体何がおかしくなったのかを物語っています。","breadcrumbs":"エラー処理 » Resultで回復可能なエラー","id":"153","title":"Resultで回復可能なエラー"},"154":{"body":"リスト9-4のコードは、File::openが失敗した理由にかかわらずpanic!します。代わりにしたいことは、 失敗理由によって動作を変えることです: ファイルが存在しないためにFile::openが失敗したら、 ファイルを作成し、その新しいファイルへのハンドルを返したいです。他の理由(例えばファイルを開く権限がなかったなど)で、 File::openが失敗したら、リスト9-4のようにコードにはpanic!してほしいのです。 リスト9-5を眺めてください。ここではmatchに別のアームを追加しています。 ファイル名: src/main.rs use std::fs::File;\nuse std::io::ErrorKind; fn main() { let f = File::open(\"hello.txt\"); let f = match f { Ok(file) => file, Err(ref error) if error.kind() == ErrorKind::NotFound => { match File::create(\"hello.txt\") { Ok(fc) => fc, Err(e) => { panic!( //ファイルを作成しようとしましたが、問題がありました \"Tried to create file but there was a problem: {:?}\", e ) }, } }, Err(error) => { panic!( \"There was a problem opening the file: {:?}\", error ) }, };\n} リスト9-5: 色々な種類のエラーを異なる方法で扱う File::openがErr列挙子に含めて返す値の型は、io::Errorであり、これは標準ライブラリで提供されている構造体です。 この構造体には、呼び出すとio::ErrorKind値が得られるkindメソッドがあります。io::ErrorKindというenumは、 標準ライブラリで提供されていて、io処理の結果発生する可能性のある色々な種類のエラーを表す列挙子があります。 使用したい列挙子は、ErrorKind::NotFoundで、これは開こうとしているファイルがまだ存在しないことを示唆します。 if error.kind() == ErrorKind::Notfoundという条件式は、 マッチガード と呼ばれます: アームのパターンをさらに洗練するmatchアーム上のおまけの条件式です。この条件式は、 そのアームのコードが実行されるには真でなければいけないのです; そうでなければ、 パターンマッチングは継続し、matchの次のアームを考慮します。パターンのrefは、 errorがガード条件式にムーブされないように必要ですが、ただ単にガード式に参照されます。 refを使用して&の代わりにパターン内で参照を作っている理由は、第18章で詳しく講義します。 手短に言えば、パターンの文脈において、&は参照にマッチし、その値を返しますが、 refは値にマッチし、それへの参照を返すということなのです。 マッチガードで精査したい条件は、error.kind()により返る値が、ErrorKind enumのNotFound列挙子であるかということです。 もしそうなら、File::createでファイル作成を試みます。ところが、File::createも失敗する可能性があるので、 内部にもmatch式を追加する必要があるのです。ファイルが開けないなら、異なるエラーメッセージが出力されるでしょう。 外側のmatchの最後のアームは同じままなので、ファイルが存在しないエラー以外ならプログラムはパニックします。","breadcrumbs":"エラー処理 » 色々なエラーにマッチする","id":"154","title":"色々なエラーにマッチする"},"155":{"body":"matchの使用は、十分に仕事をしてくれますが、いささか冗長になり得る上、必ずしも意図をよく伝えるとは限りません。 Result型には、色々な作業をするヘルパーメソッドが多く定義されています。それらの関数の一つは、 unwrapと呼ばれますが、リスト9-4で書いたmatch式と同じように実装された短絡メソッドです。 Result値がOk列挙子なら、unwrapはOkの中身を返します。ResultがErr列挙子なら、 unwrapはpanic!マクロを呼んでくれます。こちらが実際に動作しているunwrapの例です: ファイル名: src/main.rs use std::fs::File; fn main() { let f = File::open(\"hello.txt\").unwrap();\n} このコードを hello.txt ファイルなしで走らせたら、unwrapメソッドが行うpanic!呼び出しからのエラーメッセージを目の当たりにするでしょう: thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error {\nrepr: Os { code: 2, message: \"No such file or directory\" } }',\nsrc/libcore/result.rs:906:4\n('main'スレッドは、src/libcore/result.rs:906:4の\n「`Err`値に対して`Result::unwrap()`が呼び出されました: Error{\nrepr: Os { code: 2, message: \"そのようなファイルまたはディレクトリはありません\" } }」でパニックしました) 別のメソッドexpectは、unwrapに似ていますが、panic!のエラーメッセージも選択させてくれます。 unwrapの代わりにexpectを使用して、いいエラーメッセージを提供すると、意図を伝え、 パニックの原因をたどりやすくしてくれます。expectの表記はこんな感じです: ファイル名: src/main.rs use std::fs::File; fn main() { // hello.txtを開くのに失敗しました let f = File::open(\"hello.txt\").expect(\"Failed to open hello.txt\");\n} expectをunwrapと同じように使用してます: ファイルハンドルを返したり、panic!マクロを呼び出しています。 expectがpanic!呼び出しで使用するエラーメッセージは、unwrapが使用するデフォルトのpanic!メッセージではなく、 expectに渡した引数になります。以下のようになります: thread 'main' panicked at 'Failed to open hello.txt: Error { repr: Os { code:\n2, message: \"No such file or directory\" } }', src/libcore/result.rs:906:4 このエラーメッセージは、指定したテキストのhello.txtを開くのに失敗しましたで始まっているので、 コード内のどこでエラーメッセージが出力されたのかより見つけやすくなるでしょう。複数箇所でunwrapを使用していたら、 ズバリどのunwrapがパニックを引き起こしているのか理解するのは、より時間がかかる可能性があります。 パニックするunwrap呼び出しは全て、同じメッセージを出力するからです。","breadcrumbs":"エラー処理 » エラー時にパニックするショートカット: unwrapとexpect","id":"155","title":"エラー時にパニックするショートカット: unwrapとexpect"},"156":{"body":"失敗する可能性のある何かを呼び出す実装をした関数を書く際、関数内でエラーを処理する代わりに、 呼び出し元がどうするかを決められるようにエラーを返すことができます。これはエラーの 委譲 として認知され、 自分のコードの文脈で利用可能なものよりも、 エラーの処理法を規定する情報やロジックがより多くある呼び出し元のコードに制御を明け渡します。 例えば、リスト9-6の関数は、ファイルからユーザ名を読み取ります。ファイルが存在しなかったり、読み込みできなければ、 この関数はそのようなエラーを呼び出し元のコードに返します。 ファイル名: src/main.rs use std::io;\nuse std::io::Read;\nuse std::fs::File; fn read_username_from_file() -> Result { let f = File::open(\"hello.txt\"); let mut f = match f { Ok(file) => file, Err(e) => return Err(e), }; let mut s = String::new(); match f.read_to_string(&mut s) { Ok(_) => Ok(s), Err(e) => Err(e), }\n} リスト9-6: matchでエラーを呼び出し元のコードに返す関数 まずは、関数の戻り値型に注目してください: Resultです。つまり、この関数は、 Result型の値を返しているということです。ここでジェネリック引数のTは、具体型Stringで埋められ、 ジェネリック引数のEは具体型io::Errorで埋められています。この関数が何の問題もなく成功すれば、 この関数を呼び出したコードは、String(関数がファイルから読み取ったユーザ名)を保持するOk値を受け取ります。 この関数が何か問題に行き当たったら、呼び出し元のコードはio::Errorのインスタンスを保持するErr値を受け取り、 このio::Errorは問題の内容に関する情報をより多く含んでいます。関数の戻り値の型にio::Errorを選んだのは、 この関数本体で呼び出している失敗する可能性のある処理が両方とも偶然この型をエラー値として返すからです: File::open関数とread_to_stringメソッドです。 関数の本体は、File::open関数を呼び出すところから始まります。そして、リスト9-4のmatchに似たmatchで返ってくるResult値を扱い、 Errケースにpanic!を呼び出すだけの代わりに、この関数から早期リターンしてこの関数のエラー値として、 File::openから得たエラー値を呼び出し元に渡し戻します。File::openが成功すれば、 ファイルハンドルを変数fに保管して継続します。 さらに、変数sに新規Stringを生成し、fのファイルハンドルに対してread_to_stringを呼び出して、 ファイルの中身をsに読み出します。File::openが成功しても、失敗する可能性があるので、read_to_stringメソッドも、 Resultを返却します。そのResultを処理するために別のmatchが必要になります: read_to_stringが成功したら、 関数は成功し、今はOkに包まれたsに入っているファイルのユーザ名を返却します。read_to_stringが失敗したら、 File::openの戻り値を扱ったmatchでエラー値を返したように、エラー値を返します。 しかし、明示的にreturnを述べる必要はありません。これが関数の最後の式だからです。 そうしたら、呼び出し元のコードは、ユーザ名を含むOk値か、io::Errorを含むErr値を得て扱います。 呼び出し元のコードがそれらの値をどうするかはわかりません。呼び出しコードがErr値を得たら、 例えば、panic!を呼び出してプログラムをクラッシュさせたり、デフォルトのユーザ名を使ったり、 ファイル以外の場所からユーザ名を検索したりできるでしょう。呼び出し元のコードが実際に何をしようとするかについて、 十分な情報がないので、成功や失敗情報を全て委譲して適切に扱えるようにするのです。 Rustにおいて、この種のエラー委譲は非常に一般的なので、Rustにはこれをしやすくする?演算子が用意されています。 エラー委譲のショートカット: ?演算子 リスト9-7もリスト9-6と同じ機能を有するread_username_from_fileの実装ですが、 こちらは?演算子を使用しています: ファイル名: src/main.rs use std::io;\nuse std::io::Read;\nuse std::fs::File; fn read_username_from_file() -> Result { let mut f = File::open(\"hello.txt\")?; let mut s = String::new(); f.read_to_string(&mut s)?; Ok(s)\n} リスト9-7: ?演算子でエラーを呼び出し元に返す関数 Result値の直後に置かれた?は、リスト9-6でResult値を処理するために定義したmatch式とほぼ同じように動作します。 Resultの値がOkなら、Okの中身がこの式から返ってきて、プログラムは継続します。値がErrなら、 returnキーワードを使ったかのように関数全体からErrの中身が返ってくるので、 エラー値は呼び出し元のコードに委譲されます。 リスト9-6のmatch式と?演算子には違いがあります: ?を使ったエラー値は、 標準ライブラリのFromトレイトで定義され、エラーの型を別のものに変換するfrom関数を通ることです。 ?演算子がfrom関数を呼び出すと、受け取ったエラー型が現在の関数の戻り値型で定義されているエラー型に変換されます。これは、 個々がいろんな理由で失敗する可能性があるのにも関わらず、関数が失敗する可能性を全て一つのエラー型で表現して返す時に有用です。 各エラー型がfrom関数を実装して返り値のエラー型への変換を定義している限り、 ?演算子が変換の面倒を自動的に見てくれます。 リスト9-7の文脈では、File::open呼び出し末尾の?はOkの中身を変数fに返します。 エラーが発生したら、?演算子により関数全体から早期リターンし、あらゆるErr値を呼び出し元に与えます。 同じ法則がread_to_string呼び出し末尾の?にも適用されます。 ?演算子により定型コードの多くが排除され、この関数の実装を単純にしてくれます。 リスト9-8で示したように、?の直後のメソッド呼び出しを連結することでさらにこのコードを短くすることさえもできます。 ファイル名: src/main.rs use std::io;\nuse std::io::Read;\nuse std::fs::File; fn read_username_from_file() -> Result { let mut s = String::new(); File::open(\"hello.txt\")?.read_to_string(&mut s)?; Ok(s)\n} リスト9-8: ?演算子の後のメソッド呼び出しを連結する sの新規Stringの生成を関数の冒頭に移動しました; その部分は変化していません。変数fを生成する代わりに、 read_to_stringの呼び出しを直接File::open(\"hello.txt\")?の結果に連結させました。 それでも、read_to_string呼び出しの末尾には?があり、File::openとread_to_string両方が成功したら、 エラーを返すというよりもそれでも、sにユーザ名を含むOk値を返します。機能もまたリスト9-6及び、9-7と同じです; ただ単に異なるバージョンのよりエルゴノミックな書き方なのです。 ?演算子は、Resultを返す関数でしか使用できない ?演算子は戻り値にResultを持つ関数でしか使用できません。というのも、リスト9-6で定義したmatch式と同様に動作するよう、 定義されているからです。Resultの戻り値型を要求するmatchの部品は、return Err(e)なので、 関数の戻り値はこのreturnと互換性を保つためにResultでなければならないのです。 main関数で?演算子を使用したらどうなるか見てみましょう。main関数は、戻り値が()でしたね: use std::fs::File; fn main() { let f = File::open(\"hello.txt\")?;\n} このコードをコンパイルすると、以下のようなエラーメッセージが得られます: error[E0277]: the trait bound `(): std::ops::Try` is not satisfied\n(エラー: `(): std::ops::Try`というトレイト境界が満たされていません) --> src/main.rs:4:13 |\n4 | let f = File::open(\"hello.txt\")?; | ------------------------ | | | the `?` operator can only be used in a function that returns `Result` (or another type that implements `std::ops::Try`) | in this macro invocation | (このマクロ呼び出しの`Result`(かまたは`std::ops::Try`を実装する他の型)を返す関数でしか`?`演算子は使用できません) | = help: the trait `std::ops::Try` is not implemented for `()` (助言: `std::ops::Try`トレイトは`()`には実装されていません) = note: required by `std::ops::Try::from_error` (注釈: `std::ops::Try::from_error`で要求されています) このエラーは、?演算子はResultを返す関数でしか使用が許可されないと指摘しています。 Resultを返さない関数では、Resultを返す別の関数を呼び出した時、 ?演算子を使用してエラーを呼び出し元に委譲する可能性を生み出す代わりに、matchかResultのメソッドのどれかを使う必要があるでしょう。 さて、panic!呼び出しやResultを返す詳細について議論し終えたので、 どんな場合にどちらを使うのが適切か決める方法についての話に戻りましょう。","breadcrumbs":"エラー処理 » エラーを委譲する","id":"156","title":"エラーを委譲する"},"157":{"body":"では、panic!すべき時とResultを返すべき時はどう決定すればいいのでしょうか?コードがパニックしたら、 回復する手段はありません。回復する可能性のある手段の有る無しに関わらず、どんなエラー場面でもpanic!を呼ぶことはできますが、 そうすると、呼び出す側のコードの立場に立ってこの場面は回復不能だという決定を下すことになります。 Result値を返す決定をすると、決断を下すのではなく、呼び出し側に選択肢を与えることになります。 呼び出し側は、場面に合わせて回復を試みることを決定したり、この場合のErr値は回復不能と断定して、 panic!を呼び出し、回復可能だったエラーを回復不能に変換することもできます。故に、Resultを返却することは、 失敗する可能性のある関数を定義する際には、いい第一選択肢になります。 稀な場面では、Resultを返すよりもパニックするコードを書く方がより適切になることもあります。 例やプロトタイプコード、テストでパニックするのが適切な理由を探ってみましょう。 それからコンパイラではありえない失敗だと気づけなくとも、人間なら気づける場面を議論しましょう。 そして、ライブラリコードでパニックするか決定する方法についての一般的なガイドラインで結論づけましょう。","breadcrumbs":"エラー処理 » panic!すべきかするまいか","id":"157","title":"panic!すべきかするまいか"},"158":{"body":"例を記述して何らかの概念を具体化している時、頑健なエラー処理コードも例に含むことは、例の明瞭さを欠くことになりかねません。 例において、unwrapなどのパニックする可能性のあるメソッド呼び出しは、 アプリケーションにエラーを処理してほしい方法へのプレースホルダーを意味していると理解され、 これは残りのコードがしていることによって異なる可能性があります。 同様に、unwrapやexpectメソッドは、エラーの処理法を決定する準備ができる前、プロトタイプの段階では、 非常に便利です。それらにより、コードにプログラムをより頑健にする時の明らかなマーカーが残されるわけです。 メソッド呼び出しがテスト内で失敗したら、そのメソッドがテスト下に置かれた機能ではなかったとしても、 テスト全体が失敗してほしいでしょう。panic!が、テストが失敗と印づけられる手段なので、 unwrapやexpectの呼び出しはスバリ起こるべきことです。","breadcrumbs":"エラー処理 » 例、プロトタイプコード、テスト","id":"158","title":"例、プロトタイプコード、テスト"},"159":{"body":"ResultがOk値であると確認する何らかの別のロジックがある場合、unwrapを呼び出すことは適切でしょうが、 コンパイラは、そのロジックを理解はしません。それでも、処理する必要のあるResultは存在するでしょう: 呼び出している処理が何であれ、自分の特定の場面では論理的に起こり得なくても、一般的にまだ失敗する可能性はあるわけです。 手動でコードを調査してErr列挙子は存在しないと確認できたら、unwrapを呼び出すことは完全に受容できることです。 こちらが例です: use std::net::IpAddr; let home: IpAddr = \"127.0.0.1\".parse().unwrap(); ハードコードされた文字列を構文解析することでIpAddrインスタンスを生成しています。 プログラマには127.0.0.1が合法なIPアドレスであることがわかるので、ここでunwrapを使用することは、 受容可能なことです。しかしながら、ハードコードされた合法な文字列が存在することは、 parseメソッドの戻り値型を変えることにはなりません: それでも得られるのは、Result値であり、 コンパイラはまだErr列挙子になる可能性があるかのようにResultを処理することを強制してきます。 コンパイラは、この文字列が常に合法なIPアドレスであると把握できるほど利口ではないからです。 プログラムにハードコードされるのではなく、IPアドレス文字列がユーザ起源でそれ故に 確かに 失敗する可能性がある場合、 Resultをもっと頑健な方法で処理したほうが絶対にいいでしょう。","breadcrumbs":"エラー処理 » コンパイラよりもプログラマがより情報を持っている場合","id":"159","title":"コンパイラよりもプログラマがより情報を持っている場合"},"16":{"body":"Windowsでは、 https://www.rust-lang.org/install.html に行き、手順に従ってRustをインストールしてください。 インストールの途中で、Visual Studio2013以降用のC++ビルドツールも必要になるという旨のメッセージが出るでしょう。 ビルドツールを取得する最も簡単な方法は、 Visual Studio 2017用のビルドツール をインストールすることです。 ツールは、他のツール及びフレームワークのセクションにあります。 これ以降、 cmd.exe とPowerShellの両方で動くコマンドを使用します。 特定の違いがあったら、どちらを使用すべきか説明します。","breadcrumbs":"事始め » Windowsでrustupをインストールする","id":"16","title":"Windowsでrustupをインストールする"},"160":{"body":"コードが悪い状態に陥る可能性があるときにパニックさせるのは、推奨されることです。この文脈において、 悪い状態 とは、何らかの前提、保証、契約、不変性が破られたことを言い、例を挙げれば、無効な値、 矛盾する値、行方不明な値がコードに渡されることと、さらに以下のいずれか一つ以上の状態であります: 悪い状態がときに起こるとは 予想 されないとき。 この時点以降、この悪い状態にないことを頼りにコードが書かれているとき。 使用している型にこの情報をコード化するいい手段がないとき。 誰かが自分のコードを呼び出して筋の通らない値を渡してきたら、最善の選択肢はpanic!し、 開発段階で修正できるように自分たちのコードにバグがあることをライブラリ使用者に通知することかもしれません。 同様に自分の制御下にない外部コードを呼び出し、修正しようのない無効な状態を返すときにpanic!はしばしば適切です。 しかし、どんなにコードをうまく書いても起こると予想されますが、悪い状態に達したとき、それでもpanic!呼び出しをするよりも、 Resultを返すほうがより適切です。例には、不正なデータを渡されたパーサとか、 訪問制限に引っかかったことを示唆するステータスを返すHTTPリクエストなどが挙げられます。 このような場合には、呼び出し側が問題の処理方法を決定できるようにResultを返してこの悪い状態を委譲して、 失敗が予想される可能性であることを示唆するべきです。panic!を呼び出すことは、 これらのケースでは最善策ではないでしょう。 コードが値に対して処理を行う場合、コードはまず値が合法であることを確認し、 値が合法でなければパニックするべきです。これはほぼ安全性上の理由によるものです: 不正なデータの処理を試みると、 コードを脆弱性に晒す可能性があります。これが、境界外へのメモリアクセスを試みたときに標準ライブラリがpanic!を呼び出す主な理由です: 現在のデータ構造に属しないメモリにアクセスを試みることは、ありふれたセキュリティ問題なのです。 関数にはしばしば 契約 が伴います: 入力が特定の条件を満たすときのみ、振る舞いが保証されるのです。 契約が侵されたときにパニックすることは、道理が通っています。なぜなら、契約侵害は常に呼び出し側のバグを示唆し、 呼び出し側に明示的に処理してもらう必要のある種類のエラーではないからです。実際に、 呼び出し側が回復する合理的な手段はありません; 呼び出し側の プログラマ がコードを修正する必要があるのです。 関数の契約は、特に侵害がパニックを引き起こす際には、関数のAPIドキュメント内で説明されているべきです。 ですが、全ての関数でたくさんのエラーチェックを行うことは冗長で煩わしいことでしょう。幸運にも、 Rustの型システム(故にコンパイラが行う型精査)を使用して多くの検査を行ってもらうことができます。 関数の引数に特定の型があるなら、合法な値があるとコンパイラがすでに確認していることを把握して、 コードのロジックに進むことができます。例えば、Option以外の型がある場合、プログラムは、 何もない ではなく 何かある と想定します。そうしたらコードは、 SomeとNone列挙子の2つの場合を処理する必要がなくなるわけです: 確実に値があるという可能性しかありません。関数に何もないことを渡そうとしてくるコードは、 コンパイルが通りもしませんので、その場合を実行時に検査する必要はないわけです。 別の例は、u32のような符号なし整数を使うことであり、この場合、引数は負には絶対にならないことが確認されます。","breadcrumbs":"エラー処理 » エラー処理のガイドライン","id":"160","title":"エラー処理のガイドライン"},"161":{"body":"Rustの型システムを使用して合法な値があると確認するというアイディアを一歩先に進め、 検証のために独自の型を作ることに目を向けましょう。第2章の数当てゲームで、 コードがユーザに1から100までの数字を推測するよう求めたことを思い出してください。 秘密の数字と照合する前にユーザの推測がそれらの値の範囲にあることを全く確認しませんでした; 推測が正であることしか確認しませんでした。この場合、結果はそれほど悲惨なものではありませんでした: 「大きすぎ」、「小さすぎ」という出力は、それでも正しかったでしょう。ユーザを合法な推測に導き、 ユーザが範囲外の数字を推測したり、例えばユーザが文字を代わりに入力したりしたときに別の挙動をするようにしたら、 有益な改善になるでしょう。 これをする一つの方法は、ただのu32の代わりにi32として推測をパースし、負の数になる可能性を許可し、 それから数字が範囲に収まっているというチェックを追加することでしょう。そう、以下のように: loop { // --snip-- let guess: i32 = match guess.trim().parse() { Ok(num) => num, Err(_) => continue, }; if guess < 1 || guess > 100 { println!(\"The secret number will be between 1 and 100.\"); continue; } match guess.cmp(&secret_number) { // --snip--\n} このif式が、値が範囲外かどうかをチェックし、ユーザに問題を告知し、continueを呼び出してループの次の繰り返しを始め、 別の推測を求めます。if式の後、guessは1から100の範囲にあると把握して、guessと秘密の数字の比較に進むことができます。 ところが、これは理想的な解決策ではありません: プログラムが1から100の範囲の値しか処理しないことが間違いなく、 肝要であり、この要求がある関数の数が多ければ、このようなチェックを全関数で行うことは、 面倒でパフォーマンスにも影響を及ぼす可能性があるでしょう。 代わりに、新しい型を作って検証を関数内に閉じ込め、検証を全箇所で繰り返すのではなく、 その型のインスタンスを生成することができます。そうすれば、関数がその新しい型をシグニチャに用い、 受け取った値を自信を持って使用することは安全になります。リスト9-9に、new関数が1から100までの値を受け取った時のみ、 Guessのインスタンスを生成するGuess型を定義する一つの方法を示しました。 pub struct Guess { value: u32,\n} impl Guess { pub fn new(value: u32) -> Guess { if value < 1 || value > 100 { // 予想の値は1から100の範囲でなければなりませんが、{}でした panic!(\"Guess value must be between 1 and 100, got {}.\", value); } Guess { value } } pub fn value(&self) -> u32 { self.value }\n} リスト9-9: 値が1から100の場合のみ処理を継続するGuess型 まず、u32型のvalueをフィールドに持つGuessという名前の構造体を定義しています。 ここに数値が保管されます。 それからGuessにGuess値のインスタンスを生成するnewという名前の関連関数を実装しています。 new関数は、u32型のvalueという引数を取り、Guessを返すように定義されています。 new関数の本体のコードは、valueをふるいにかけ、1から100の範囲であることを確かめます。 valueがふるいに引っかかったら、panic!呼び出しを行います。これにより、呼び出しコードを書いているプログラマに、 修正すべきバグがあると警告します。というのも、この範囲外のvalueでGuessを生成することは、 Guess::newが頼りにしている契約を侵害するからです。Guess::newがパニックするかもしれない条件は、 公開されているAPIドキュメントで議論されるべきでしょう; あなたが作成するAPIドキュメントでpanic!の可能性を示唆する、 ドキュメントの規約は、第14章で講義します。valueが確かにふるいを通ったら、 valueフィールドがvalue引数にセットされた新しいGuessを作成して返します。 次に、selfを借用し、他に引数はなく、u32を返すvalueというメソッドを実装します。 この類のメソッドは時に ゲッター と呼ばれます。目的がフィールドから何らかのデータを得て返すことだからです。 この公開メソッドは、Guess構造体のvalueフィールドが非公開なので、必要になります。 valueフィールドが非公開なことは重要であり、そのためにGuess構造体を使用するコードは、 直接valueをセットすることが叶わないのです: モジュール外のコードは、 Guess::new関数を使用してGuessのインスタンスを生成し なければならず 、 それにより、Guess::new関数の条件式でチェックされていないvalueがGuessに存在する手段はないことが保証されるわけです。 そうしたら、引数を一つ持つか、1から100の範囲の数値のみを返す関数は、シグニチャでu32ではなく、 Guessを取るか返し、本体内で追加の確認を行う必要はなくなると宣言できるでしょう。","breadcrumbs":"エラー処理 » 検証のために独自の型を作る","id":"161","title":"検証のために独自の型を作る"},"162":{"body":"Rustのエラー処理機能は、プログラマがより頑健なコードを書く手助けをするように設計されています。 panic!マクロは、プログラムが処理できない状態にあり、無効だったり不正な値で処理を継続するのではなく、 プロセスに処理を中止するよう指示することを通知します。Result enumは、Rustの型システムを使用して、 コードが回復可能な方法で処理が失敗するかもしれないことを示唆します。Resultを使用して、 呼び出し側のコードに成功や失敗する可能性を処理する必要があることも教えます。 適切な場面でpanic!やResultを使用することで、必然的な問題の眼前でコードの信頼性を上げてくれます。 今や、標準ライブラリがOptionやResult enumなどでジェネリクスを有効活用するところを目の当たりにしたので、 ジェネリクスの動作法と自分のコードでの使用方法について語りましょう。","breadcrumbs":"エラー処理 » まとめ","id":"162","title":"まとめ"},"163":{"body":"全てのプログラミング言語には、概念の重複を効率的に扱う道具があります。Rustにおいて、そのような道具の一つが ジェネリクス です。 ジェネリクスは、具体型や他のプロパティの抽象的な代役です。コード記述の際、コンパイルやコード実行時に、 ジェネリクスの位置に何が入るかを知ることなく、ジェネリクスの振る舞いや他のジェネリクスとの関係を表現できるのです。 関数が未知の値の引数を取り、同じコードを複数の具体的な値に対して走らせるように、 i32やStringなどの具体的な型の代わりに何かジェネリックな型の引数を取ることができます。 実際、第6章でOption、第8章でVecとHashMap、第9章でResultを既に使用しました。 この章では、独自の型、関数、メソッドをジェネリクスとともに定義する方法を探究します! まず、関数を抽出して、コードの重複を減らす方法を確認しましょう。次に同じテクニックを活用して、 引数の型のみが異なる2つの関数からジェネリックな関数を生成します。また、 ジェネリックな型を構造体やenum定義で使用する方法も説明します。 それから、トレイトを使用して、ジェネリックな方法で振る舞いを定義する方法を学びます。 ジェネリックな型にトレイトを組み合わせることで、ジェネリックな型を、単にあらゆる型に対してではなく、特定の振る舞いのある型のみに制限できます。 最後に、ライフタイムを議論します。ライフタイムとは、コンパイラに参照がお互いにどう関係しているかの情報を与える一種のジェネリクスです。 ライフタイムのおかげでコンパイラに参照が有効であることを確認してもらうことを可能にしつつ、多くの場面で値を借用できます。","breadcrumbs":"ジェネリック型、トレイト、ライフタイム","id":"163","title":"ジェネリック型、トレイト、ライフタイム"},"164":{"body":"ジェネリクスの記法に飛び込む前にまずは、関数を抽出することでジェネリックな型が関わらない重複を取り除く方法を見ましょう。 そして、このテクニックを適用してジェネリックな関数を抽出するのです!重複したコードを認識して関数に抽出できるのと同じように、 ジェネリクスを使用できる重複コードも認識し始めるでしょう。 リスト10-1に示したように、リスト内の最大値を求める短いプログラムを考えてください。 ファイル名: src/main.rs fn main() { let number_list = vec![34, 50, 25, 100, 65]; let mut largest = number_list[0]; for number in number_list { if number > largest { largest = number; } } // 最大値は{}です println!(\"The largest number is {}\", largest);\n# assert_eq!(largest, 100);\n} リスト10-1: 数字のリストから最大値を求めるコード このコードは、整数のリストを変数number_listに格納し、リストの最初の数字をlargestという変数に配置しています。 それからリストの数字全部を走査し、現在の数字がlargestに格納された数値よりも大きければ、 その変数の値を置き換えます。ですが、現在の数値が今まで見た最大値よりも小さければ、 変数は変わらず、コードはリストの次の数値に移っていきます。リストの数値全てを吟味した後、 largestは最大値を保持しているはずで、今回は100になります。 2つの異なる数値のリストから最大値を発見するには、リスト10-1のコードを複製し、 プログラムの異なる2箇所で同じロジックを使用できます。リスト10-2のようにですね。 ファイル名: src/main.rs fn main() { let number_list = vec![34, 50, 25, 100, 65]; let mut largest = number_list[0]; for number in number_list { if number > largest { largest = number; } } println!(\"The largest number is {}\", largest); let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8]; let mut largest = number_list[0]; for number in number_list { if number > largest { largest = number; } } println!(\"The largest number is {}\", largest);\n} リスト10-2: 2つ の数値のリストから最大値を探すコード このコードは動くものの、コードを複製することは退屈ですし、間違いも起きやすいです。また、 コードを変更したい時に複数箇所、更新しなければなりません。 この重複を排除するには、引数で与えられた整数のどんなリストに対しても処理が行える関数を定義して抽象化できます。 この解決策によりコードがより明確になり、リストの最大値を探すという概念を抽象的に表現させてくれます。 リスト10-3では、最大値を探すコードをlargestという関数に抽出しました。リスト10-1のコードは、 たった1つの特定のリストからだけ最大値を探せますが、それとは異なり、このプログラムは2つの異なるリストから最大値を探せます。 ファイル名: src/main.rs fn largest(list: &[i32]) -> i32 { let mut largest = list[0]; for &item in list.iter() { if item > largest { largest = item; } } largest\n} fn main() { let number_list = vec![34, 50, 25, 100, 65]; let result = largest(&number_list); println!(\"The largest number is {}\", result);\n# assert_eq!(result, 100); let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8]; let result = largest(&number_list); println!(\"The largest number is {}\", result);\n# assert_eq!(result, 6000);\n} リスト10-3: 2つのリストから最大値を探す抽象化されたコード largest関数にはlistと呼ばれる引数があり、これは、関数に渡す可能性のある、あらゆるi32値の具体的なスライスを示します。 結果的に、関数呼び出しの際、コードは渡した特定の値に対して走るのです。 まとめとして、こちらがリスト10-2のコードからリスト10-3に変更するのに要したステップです: 重複したコードを見分ける。 重複コードを関数本体に抽出し、コードの入力と戻り値を関数シグニチャで指定する。 重複したコードの2つの実体を代わりに関数を呼び出すように更新する。 次は、この同じ手順をジェネリクスでも踏んで異なる方法でコードの重複を減らします。 関数本体が特定の値ではなく抽象的なlistに対して処理できたのと同様に、 ジェネリクスは抽象的な型に対して処理するコードを可能にしてくれます。 例えば、関数が2つあるとしましょう: 1つはi32値のスライスから最大の要素を探し、1つはchar値のスライスから最大要素を探します。 この重複はどう排除するのでしょうか?答えを見つけましょう!","breadcrumbs":"関数を抽出することで重複を取り除く","id":"164","title":"関数を抽出することで重複を取り除く"},"165":{"body":"関数シグニチャや構造体などの要素の定義を生成するのにジェネリクスを使用することができ、 それはさらに他の多くの具体的なデータ型と使用することもできます。まずは、 ジェネリクスで関数、構造体、enum、メソッドを定義する方法を見ましょう。それから、 ジェネリクスがコードのパフォーマンスに与える影響を議論します。","breadcrumbs":"ジェネリック型、トレイト、ライフタイム » ジェネリックなデータ型","id":"165","title":"ジェネリックなデータ型"},"166":{"body":"ジェネリクスを使用する関数を定義する時、通常、引数や戻り値のデータ型を指定する関数のシグニチャにジェネリクスを配置します。 そうすることでコードがより柔軟になり、コードの重複を阻止しつつ、関数の呼び出し元により多くの機能を提供します。 largest関数を続けます。リスト10-4はどちらもスライスから最大値を探す2つの関数を示しています。 ファイル名: src/main.rs fn largest_i32(list: &[i32]) -> i32 { let mut largest = list[0]; for &item in list.iter() { if item > largest { largest = item; } } largest\n} fn largest_char(list: &[char]) -> char { let mut largest = list[0]; for &item in list.iter() { if item > largest { largest = item; } } largest\n} fn main() { let number_list = vec![34, 50, 25, 100, 65]; let result = largest_i32(&number_list); println!(\"The largest number is {}\", result);\n# assert_eq!(result, 100); let char_list = vec!['y', 'm', 'a', 'q']; let result = largest_char(&char_list); println!(\"The largest char is {}\", result);\n# assert_eq!(result, 'y');\n} リスト10-4: 名前とシグニチャの型のみが異なる2つの関数 largest_i32関数は、リスト10-3で抽出したスライスから最大のi32を探す関数です。 largest_char関数は、スライスから最大のcharを探します。関数本体には同じコードがあるので、 単独の関数にジェネリックな型引数を導入してこの重複を排除しましょう。 これから定義する新しい関数の型を引数にするには、ちょうど関数の値引数のように型引数に名前をつける必要があります。 型引数の名前にはどんな識別子も使用できますが、Tを使用します。というのも、慣習では、 Rustの引数名は短く(しばしばたった1文字になります)、Rustの型の命名規則がキャメルケースだからです。 \"type\"の省略形なので、Tが多くのRustプログラマの既定の選択なのです。 関数の本体で引数を使用するとき、コンパイラがその名前の意味を把握できるようにシグニチャでその引数名を宣言しなければなりません。 同様に、型引数名を関数シグニチャで使用する際には、使用する前に型引数名を宣言しなければなりません。 ジェネリックなlargest関数を定義するために、型名宣言を山カッコ(<>)内、関数名と引数リストの間に配置してください。 こんな感じに: fn largest(list: &[T]) -> T { この定義は以下のように解読します: 関数largestは、なんらかの型Tに関してジェネリックであると。 この関数にはlistという引数が1つあり、これは型Tの値のスライスです。 largest関数は同じT型の値を返します。 リスト10-5は、シグニチャにジェネリックなデータ型を使用してlargest関数定義を組み合わせたものを示しています。 このリストはさらに、この関数をi32値かchar値のどちらかで呼べる方法も表示しています。 このコードはまだコンパイルできないことに注意してください。ですが、この章の後ほど修正します。 ファイル名: src/main.rs fn largest(list: &[T]) -> T { let mut largest = list[0]; for &item in list.iter() { if item > largest { largest = item; } } largest\n} fn main() { let number_list = vec![34, 50, 25, 100, 65]; let result = largest(&number_list); println!(\"The largest number is {}\", result); let char_list = vec!['y', 'm', 'a', 'q']; let result = largest(&char_list); println!(\"The largest char is {}\", result);\n} リスト10-5: ジェネリックな型引数を使用するものの、まだコンパイルできないlargest関数の定義 直ちにこのコードをコンパイルしたら、以下のようなエラーが出ます: error[E0369]: binary operation `>` cannot be applied to type `T`\n(エラー: 2項演算`>`は、型`T`に適用できません) --> src/main.rs:5:12 |\n5 | if item > largest { | ^^^^^^^^^^^^^^ | = note: an implementation of `std::cmp::PartialOrd` might be missing for `T` (注釈: `std::cmp::PartialOrd`の実装が`T`に対して存在しない可能性があります) 注釈がstd::cmp::PartialOrdに触れています。これは、 トレイト です。トレイトについては、次の節で語ります。 とりあえず、このエラーは、largestの本体は、Tがなりうる全ての可能性のある型に対して動作しないと述べています。 本体で型Tの値を比較したいので、値が順序付け可能な型のみしか使用できないのです。比較を可能にするために、 標準ライブラリには型に実装できるstd::cmp::PartialOrdトレイトがあります(このトレイトについて詳しくは付録Cを参照されたし)。 ジェネリックな型が特定のトレイトを持つと指定する方法は「トレイト境界」節で習うでしょうが、 先にジェネリックな型引数を使用する他の方法を探究しましょう。","breadcrumbs":"ジェネリック型、トレイト、ライフタイム » 関数定義では","id":"166","title":"関数定義では"},"167":{"body":"構造体を定義して<>記法で1つ以上のフィールドにジェネリックな型引数を使用することもできます。 リスト10-6は、Point構造体を定義してあらゆる型のxとy座標を保持する方法を示しています。 ファイル名: src/main.rs struct Point { x: T, y: T,\n} fn main() { let integer = Point { x: 5, y: 10 }; let float = Point { x: 1.0, y: 4.0 };\n} リスト10-6: 型Tのxとy値を保持するPoint構造体 構造体定義でジェネリクスを使用する記法は、関数定義のものと似ています。まず、山カッコ内に型引数の名前を構造体名の直後に宣言します。 そうすると、本来具体的なデータ型を記述する構造体定義の箇所に、ジェネリックな型を使用できます。 ジェネリックな型を1つだけ使用してPointを定義したので、この定義は、Point構造体がなんらかの型Tに関して、 ジェネリックであると述べていて、その型がなんであれ、xとyのフィールドは 両方 その同じ型になっていることに注意してください。 リスト10-7のように、異なる型の値のあるPointのインスタンスを生成すれば、コードはコンパイルできません。 ファイル名: src/main.rs struct Point { x: T, y: T,\n} fn main() { let wont_work = Point { x: 5, y: 4.0 };\n} リスト10-7: どちらも同じジェネリックなデータ型Tなので、xとyというフィールドは同じ型でなければならない この例で、xに整数値5を代入すると、このPointのインスタンスに対するジェネリックな型Tは整数になるとコンパイラに知らせます。 それからyに4.0を指定する時に、このフィールドはxと同じ型と定義したはずなので、このように型不一致エラーが出ます: error[E0308]: mismatched types --> src/main.rs:7:38 |\n7 | let wont_work = Point { x: 5, y: 4.0 }; | ^^^ expected integral variable, found\nfloating-point variable | = note: expected type `{integer}` found type `{float}` xとyが両方ジェネリックだけれども、異なる型になり得るPoint構造体を定義するには、 複数のジェネリックな型引数を使用できます。例えば、リスト10-8では、Pointの定義を変更して、 型TとUに関してジェネリックにし、xが型Tで、yが型Uになります。 ファイル名: src/main.rs struct Point { x: T, y: U,\n} fn main() { let both_integer = Point { x: 5, y: 10 }; let both_float = Point { x: 1.0, y: 4.0 }; let integer_and_float = Point { x: 5, y: 4.0 };\n} リスト10-8: Pointは2つの型に関してジェネリックなので、xとyは異なる型の値になり得る これで、示されたPointインスタンスは全部使用可能です!所望の数だけ定義でジェネリックな型引数を使用できますが、 数個以上使用すると、コードが読みづらくなります。コードで多くのジェネリックな型が必要な時は、 コードの小分けが必要なサインかもしれません。","breadcrumbs":"ジェネリック型、トレイト、ライフタイム » 構造体定義では","id":"167","title":"構造体定義では"},"168":{"body":"構造体のように、列挙子にジェネリックなデータ型を保持するenumを定義することができます。 標準ライブラリが提供しているOption enumをもう一度見ましょう。このenumは第6章で使用しました: enum Option { Some(T), None,\n} この定義はもう、あなたにとってより道理が通っているはずです。ご覧の通り、Optionは、 型Tに関してジェネリックで2つの列挙子のあるenumです: その列挙子は、型Tの値を保持するSomeと、 値を何も保持しないNoneです。Option enumを使用することで、オプショナルな値があるという抽象的な概念を表現でき、 Optionはジェネリックなので、オプショナルな値の型に関わらず、この抽象を使用できます。 enumも複数のジェネリックな型を使用できます。第9章で使用したResult enumの定義が一例です: enum Result { Ok(T), Err(E),\n} Result enumは2つの型T、Eに関してジェネリックで、2つの列挙子があります: 型Tの値を保持するOkと、 型Eの値を保持するErrです。この定義により、Result enumを、成功する(なんらかの型Tの値を返す)か、 失敗する(なんらかの型Eのエラーを返す)可能性のある処理がある、あらゆる箇所に使用するのが便利になります。 事実、ファイルを開くのに成功した時にTに型std::fs::Fileが入り、ファイルを開く際に問題があった時にEに型std::io::Errorが入ったものが、 リスト9-3でファイルを開くのに使用したものです。 自分のコード内で、保持している値の型のみが異なる構造体やenum定義の場面を認識したら、 代わりにジェネリックな型を使用することで重複を避けることができます。","breadcrumbs":"ジェネリック型、トレイト、ライフタイム » enum定義では","id":"168","title":"enum定義では"},"169":{"body":"(第5章のように、)定義にジェネリックな型を使うメソッドを構造体やenumに実装することもできます。リスト10-9は、 リスト10-6で定義したPoint構造体にxというメソッドを実装したものを示しています。 ファイル名: src/main.rs struct Point { x: T, y: T,\n} impl Point { fn x(&self) -> &T { &self.x }\n} fn main() { let p = Point { x: 5, y: 10 }; println!(\"p.x = {}\", p.x());\n} リスト10-9: 型Tのxフィールドへの参照を返すxというメソッドをPoint構造体に実装する ここで、フィールドxのデータへの参照を返すxというメソッドをPointに定義しました。 implの直後にTを宣言しなければならないことに注意してください。こうすることで、型Pointにメソッドを実装していることを指定するために、Tを使用することができます。 implの後にTをジェネリックな型として宣言することで、コンパイラは、Pointの山カッコ内の型が、 具体的な型ではなくジェネリックな型であることを認識できるのです。 例えば、ジェネリックな型を持つPointインスタンスではなく、Pointだけにメソッドを実装することもできるでしょう。 リスト10-10では、具体的な型f32を使用しています。つまり、implの後に型を宣言しません。 # struct Point {\n# x: T,\n# y: T,\n# }\n#\nimpl Point { fn distance_from_origin(&self) -> f32 { (self.x.powi(2) + self.y.powi(2)).sqrt() }\n} リスト10-10: ジェネリックな型引数Tに対して特定の具体的な型がある構造体にのみ適用されるimplブロック このコードは、Pointにはdistance_from_originというメソッドが存在するが、 Tがf32ではないPointの他のインスタンスにはこのメソッドが定義されないことを意味します。 このメソッドは、この点が座標(0.0, 0.0)の点からどれだけ離れているかを測定し、 浮動小数点数にのみ利用可能な数学的処理を使用します。 構造体定義のジェネリックな型引数は、必ずしもその構造体のメソッドシグニチャで使用するものと同じにはなりません。 例を挙げれば、リスト10-11は、リスト10-8のPointにメソッドmixupを定義しています。 このメソッドは、他のPointを引数として取り、この引数はmixupを呼び出しているselfのPointとは異なる型の可能性があります。 このメソッドは、(型Tの)selfのPointのx値と渡した(型Wの)Pointのy値から新しいPointインスタンスを生成します。 ファイル名: src/main.rs struct Point { x: T, y: U,\n} impl Point { fn mixup(self, other: Point) -> Point { Point { x: self.x, y: other.y, } }\n} fn main() { let p1 = Point { x: 5, y: 10.4 }; let p2 = Point { x: \"Hello\", y: 'c'}; let p3 = p1.mixup(p2); println!(\"p3.x = {}, p3.y = {}\", p3.x, p3.y);\n} リスト10-11: 構造体定義とは異なるジェネリックな型を使用するメソッド mainで、x(値は5)にi32、y(値は10.4)にf64を持つPointを定義しました。p2変数は、 x(値は\"Hello\")に文字列スライス、y(値はc)にcharを持つPoint構造体です。 引数p2でp1にmixupを呼び出すと、p3が得られ、xはi32になります。xはp1由来だからです。 p3変数のyは、charになります。yはp2由来だからです。println!マクロの呼び出しは、 p3.x = 5, p3.y = cと出力するでしょう。 この例の目的は、一部のジェネリックな引数はimplで宣言され、他の一部はメソッド定義で宣言される場面をデモすることです。 ここで、ジェネリックな引数TとUはimplの後に宣言されています。構造体定義にはまるからです。 ジェネリックな引数VとWはfn mixupの後に宣言されています。何故なら、このメソッドにしか関係ないからです。","breadcrumbs":"ジェネリック型、トレイト、ライフタイム » メソッド定義では","id":"169","title":"メソッド定義では"},"17":{"body":"rustup経由でRustをインストールしたら、最新版への更新は、簡単になります。シェルから、 以下の更新スクリプトを実行してください: $ rustup update Rustとrustupをアンインストールするには、シェルから以下のアンインストールスクリプトを実行してください: $ rustup self uninstall","breadcrumbs":"事始め » 更新及びアンインストール","id":"17","title":"更新及びアンインストール"},"170":{"body":"ジェネリックな型引数を使用すると、実行時にコストが発生するのかな、と思うかもしれません。 嬉しいことにRustでは、ジェネリクスを、具体的な型があるコードよりもジェネリックな型を使用したコードを実行するのが遅くならないように実装しています。 コンパイラはこれを、ジェネリクスを使用しているコードの単相化をコンパイル時に行うことで達成しています。 単相化 (monomorphization)は、コンパイル時に使用されている具体的な型を入れることで、 ジェネリックなコードを特定のコードに変換する過程のことです。 この過程において、コンパイラは、リスト10-5でジェネリックな関数を生成するために使用した手順と真逆のことをしています: コンパイラは、ジェネリックなコードが呼び出されている箇所全部を見て、 ジェネリックなコードが呼び出されている具体的な型のコードを生成するのです。 標準ライブラリのOption enumを使用する例でこれが動作する方法を見ましょう: let integer = Some(5);\nlet float = Some(5.0); コンパイラがこのコードをコンパイルすると、単相化を行います。その過程で、コンパイラはOptionのインスタンスに使用された値を読み取り、 2種類のOptionを識別します: 一方はi32で、もう片方はf64です。そのように、 コンパイラは、Optionのジェネリックな定義をOption_i32とOption_f64に展開し、 それにより、ジェネリックな定義を特定の定義と置き換えます。 単相化されたバージョンのコードは、以下のようになります。ジェネリックなOptionが、 コンパイラが生成した特定の定義に置き換えられています: ファイル名: src/main.rs enum Option_i32 { Some(i32), None,\n} enum Option_f64 { Some(f64), None,\n} fn main() { let integer = Option_i32::Some(5); let float = Option_f64::Some(5.0);\n} Rustでは、ジェネリックなコードを各インスタンスで型を指定したコードにコンパイルするので、 ジェネリクスを使用することに対して実行時コストを払うことはありません。コードを実行すると、 それぞれの定義を手作業で複製した時のように振る舞います。単相化の過程により、 Rustのジェネリクスは実行時に究極的に効率的になるのです。","breadcrumbs":"ジェネリック型、トレイト、ライフタイム » ジェネリクスを使用したコードのパフォーマンス","id":"170","title":"ジェネリクスを使用したコードのパフォーマンス"},"171":{"body":"トレイト は、Rustコンパイラに、特定の型に存在し、他の型と共有できる機能について知らせます。 トレイトを使用すると、共通の振る舞いを抽象的に定義できます。トレイト境界を使用すると、 あるジェネリックが、特定の振る舞いをもつあらゆる型になり得ることを指定できます。 注釈: 違いはあるものの、トレイトは他の言語でよくインターフェイスと呼ばれる機能に類似しています。","breadcrumbs":"ジェネリック型、トレイト、ライフタイム » トレイト: 共通の振る舞いを定義する","id":"171","title":"トレイト: 共通の振る舞いを定義する"},"172":{"body":"型の振る舞いは、その型に対して呼び出せるメソッドから構成されます。異なる型は、それらの型全てに対して同じメソッドを呼び出せるなら、 同じ振る舞いを共有することになります。トレイト定義は、メソッドシグニチャをあるグループにまとめ、なんらかの目的を達成するのに必要な一連の振る舞いを定義する手段です。 例えば、いろんな種類や量のテキストを保持する複数の構造体があるとしましょう: 特定の場所から送られる新しいニュースを保持するNewsArticleと、 新規ツイートか、リツイートか、はたまた他のツイートへのリプライなのかを示すメタデータを伴う最大で280文字までのTweetです。 NewsArticle または Tweet インスタンスに保存されているデータのサマリーを表示できるメディア アグリゲータ ライブラリを作成します。 これをするには、各型のサマリーが必要で、インスタンスで summarize メソッドを呼び出してサマリーを要求する必要があります。 リスト10-12は、この振る舞いを表現するSummaryトレイトの定義を表示しています。 ファイル名: src/lib.rs pub trait Summary { fn summarize(&self) -> String;\n} リスト10-12: summarizeメソッドで提供される振る舞いからなるSummaryトレイト ここでは、traitキーワード、それからトレイト名を使用してトレイトを定義していて、その名前は今回の場合、 Summaryです。波括弧の中にこのトレイトを実装する型の振る舞いを記述するメソッドシグニチャを定義し、 今回の場合は、fn summarize(&self) -> Stringです。 メソッドシグニチャの後に、波括弧内に実装を提供する代わりに、セミコロンを使用しています。 このトレイトを実装する型はそれぞれ、メソッドの本体に独自の振る舞いを提供しなければなりません。 コンパイラにより、Summaryトレイトを保持するあらゆる型に、このシグニチャと全く同じメソッドsummarizeが定義されていることが 強制されます。 トレイトには、本体に複数のメソッドを含むことができます: メソッドシグニチャは行ごとに並べられ、 各行はセミコロンで終わります。","breadcrumbs":"ジェネリック型、トレイト、ライフタイム » トレイトを定義する","id":"172","title":"トレイトを定義する"},"173":{"body":"今や Summary トレイトを使用して目的の動作を定義できたので、メディア アグリゲータでこれを型に実装できます。 リスト10-13は、 Summary トレイトを NewsArticle 構造体上に実装したもので、ヘッドライン、著者、そして地域情報を使ってsummarize の戻り値を作っています。 Tweet 構造体に関しては、ツイートの内容が既に280文字に制限されていると仮定して、ユーザー名の後にツイートのテキスト全体が続くものとして summarize を定義します。 ファイル名: src/lib.rs # pub trait Summary {\n# fn summarize(&self) -> String;\n# }\n# pub struct NewsArticle { pub headline: String, pub location: String, pub author: String, pub content: String,\n} impl Summary for NewsArticle { fn summarize(&self) -> String { format!(\"{}, by {} ({})\", self.headline, self.author, self.location) }\n} pub struct Tweet { pub username: String, pub content: String, pub reply: bool, pub retweet: bool,\n} impl Summary for Tweet { fn summarize(&self) -> String { format!(\"{}: {}\", self.username, self.content) }\n} リスト10-13: SummaryトレイトをNewsArticleとTweet型に実装する 型にトレイトを実装することは、普通のメソッドを実装することに似ています。違いは、implの後に、 実装したいトレイトの名前を置き、それからforキーワード、さらにトレイトの実装対象の型の名前を指定することです。 implブロック内に、トレイト定義で定義したメソッドシグニチャを置きます。各シグニチャの後にセミコロンを追記するのではなく、 波括弧を使用し、メソッド本体に特定の型のトレイトのメソッドに欲しい特定の振る舞いを入れます。 トレイトを実装後、普通のメソッド同様にNewsArticleやTweetのインスタンスに対してこのメソッドを呼び出せます。 こんな感じで: # use chapter10::{self, Summary, Tweet};\n# # fn main() { let tweet = Tweet { username: String::from(\"horse_ebooks\"), content: String::from( // もちろん、ご存知かもしれませんがね、みなさん \"of course, as you probably already know, people\", ), reply: false, retweet: false, }; println!(\"1 new tweet: {}\", tweet.summarize());\n# } このコードは、1 new tweet: horse_ebooks: of course, as you probably already know, peopleと出力します。 リスト10-13でSummaryトレイトとNewArticle、Tweet型を同じ lib.rs に定義したので、 全部同じスコープにあることに注目してください。この lib.rs をaggregatorと呼ばれるクレート専用にして、 誰か他の人が私たちのクレートの機能を活用して自分のライブラリのスコープに定義された構造体にSummaryトレイトを実装したいとしましょう。 まず、トレイトをスコープに取り込む必要があるでしょう。use aggregator::Summary;と指定してそれを行えば、 これにより、自分の型にSummaryを実装することが可能になるでしょう。Summaryトレイトは、 他のクレートが実装するためには、公開トレイトである必要があり、ここでは、リスト10-12のtraitの前に、 pubキーワードを置いたのでそうなっています。 トレイト実装で注意すべき制限の1つは、トレイトか対象の型が自分のクレートに固有(local)である時のみ、 型に対してトレイトを実装できるということです。例えば、Displayのような標準ライブラリのトレイトをaggregatorクレートの機能の一部として、 Tweetのような独自の型に実装できます。型Tweetがaggregatorクレートに固有だからです。 また、SummaryをaggregatorクレートでVecに対して実装することもできます。 トレイトSummaryは、aggregatorクレートに固有だからです。 しかし、外部のトレイトを外部の型に対して実装することはできません。例として、 aggregatorクレート内でVecに対してDisplayトレイトを実装することはできません。 DisplayとVecは標準ライブラリで定義され、aggregatorクレートに固有ではないからです。 この制限は、 コヒーレンス (coherence)、特に 孤児のルール (orphan rule)と呼ばれるプログラムの特性の一部で、 親の型が存在しないためにそう命名されました。この規則により、他の人のコードが自分のコードを壊したり、 その逆が起きないことを保証してくれます。この規則がなければ、2つのクレートが同じ型に対して同じトレイトを実装できてしまい、 コンパイラはどちらの実装を使うべきかわからなくなってしまうでしょう。","breadcrumbs":"ジェネリック型、トレイト、ライフタイム » トレイトを型に実装する","id":"173","title":"トレイトを型に実装する"},"174":{"body":"時として、全ての型の全メソッドに対して実装を要求するのではなく、トレイトの全てあるいは一部のメソッドに対してデフォルトの振る舞いがあると有用です。 そうすれば、特定の型にトレイトを実装する際、各メソッドのデフォルト実装を保持するかオーバーライドするか選べるわけです。 リスト10-14は、リスト10-12のように、メソッドシグニチャだけを定義するのではなく、 Summaryトレイトのsummarizeメソッドにデフォルトの文字列を指定する方法を示しています。 ファイル名: src/lib.rs pub trait Summary { fn summarize(&self) -> String { // \"(もっと読む)\" String::from(\"(Read more...)\") }\n}\n# # pub struct NewsArticle {\n# pub headline: String,\n# pub location: String,\n# pub author: String,\n# pub content: String,\n# }\n# # impl Summary for NewsArticle {}\n# # pub struct Tweet {\n# pub username: String,\n# pub content: String,\n# pub reply: bool,\n# pub retweet: bool,\n# }\n# # impl Summary for Tweet {\n# fn summarize(&self) -> String {\n# format!(\"{}: {}\", self.username, self.content)\n# }\n# } リスト10-14: summarizeメソッドのデフォルト実装があるSummaryトレイトの定義 独自の実装を定義するのではなく、デフォルト実装を利用してNewsArticleのインスタンスをまとめるには、 impl Summary for NewsArticle {}と空のimplブロックを指定します。 もはやNewsArticleに直接summarizeメソッドを定義してはいませんが、私達はデフォルト実装を提供しており、 NewsArticleはSummaryトレイトを実装すると指定しました。そのため、 NewsArticleのインスタンスに対してsummarizeメソッドを同じように呼び出すことができます。 このように: # use chapter10::{self, NewsArticle, Summary};\n# # fn main() { let article = NewsArticle { // ペンギンチームがスタンレーカップチャンピオンシップを勝ち取る! headline: String::from(\"Penguins win the Stanley Cup Championship!\"), // アメリカ、ペンシルベニア州、ピッツバーグ location: String::from(\"Pittsburgh, PA, USA\"), // アイスバーグ author: String::from(\"Iceburgh\"), // ピッツバーグ・ペンギンが再度NHL(National Hockey League)で最強のホッケーチームになった content: String::from( \"The Pittsburgh Penguins once again are the best \\ hockey team in the NHL.\", ), }; println!(\"New article available! {}\", article.summarize());\n# } このコードは、New article available! (Read more...)(新しい記事があります!(もっと読む))と出力します。 summarizeにデフォルト実装を用意しても、リスト10-13のTweetのSummary実装を変える必要はありません。 理由は、デフォルト実装をオーバーライドする記法はデフォルト実装のないトレイトメソッドを実装する記法と同じだからです。 デフォルト実装は、自らのトレイトのデフォルト実装を持たない他のメソッドを呼び出すことができます。 このようにすれば、トレイトは多くの有用な機能を提供しつつ、実装者は僅かな部分しか指定しなくて済むようになります。 例えば、Summaryトレイトを、(実装者が)内容を実装しなければならないsummarize_authorメソッドを持つように定義し、 それからsummarize_authorメソッドを呼び出すデフォルト実装を持つsummarizeメソッドを定義することもできます: pub trait Summary { fn summarize_author(&self) -> String; fn summarize(&self) -> String { // \"({}さんの文章をもっと読む)\" format!(\"(Read more from {}...)\", self.summarize_author()) }\n}\n# # pub struct Tweet {\n# pub username: String,\n# pub content: String,\n# pub reply: bool,\n# pub retweet: bool,\n# }\n# # impl Summary for Tweet {\n# fn summarize_author(&self) -> String {\n# format!(\"@{}\", self.username)\n# }\n# } このバージョンのSummaryを使用するために、型にトレイトを実装する際、実装する必要があるのはsummarize_authorだけです: # pub trait Summary {\n# fn summarize_author(&self) -> String;\n# # fn summarize(&self) -> String {\n# // \"({}さんの文章をもっと読む)\"\n# format!(\"(Read more from {}...)\", self.summarize_author())\n# }\n# }\n# # pub struct Tweet {\n# pub username: String,\n# pub content: String,\n# pub reply: bool,\n# pub retweet: bool,\n# }\n# impl Summary for Tweet { fn summarize_author(&self) -> String { format!(\"@{}\", self.username) }\n} summarize_author定義後、Tweet構造体のインスタンスに対してsummarizeを呼び出せ、 summarizeのデフォルト実装は、私達が提供したsummarize_authorの定義を呼び出すでしょう。 summarize_authorを実装したので、追加のコードを書く必要なく、Summaryトレイトは、 summarizeメソッドの振る舞いを与えてくれました。 # use chapter10::{self, Summary, Tweet};\n# # fn main() { let tweet = Tweet { username: String::from(\"horse_ebooks\"), content: String::from( \"of course, as you probably already know, people\", ), reply: false, retweet: false, }; println!(\"1 new tweet: {}\", tweet.summarize());\n# } このコードは、1 new tweet: (Read more from @horse_ebooks...)(1つの新しいツイート:(@horse_ebooksさんの文章をもっと読む))と出力します。 デフォルト実装を、そのメソッドをオーバーライドしている実装から呼び出すことはできないことに注意してください。","breadcrumbs":"ジェネリック型、トレイト、ライフタイム » デフォルト実装","id":"174","title":"デフォルト実装"},"175":{"body":"トレイトを定義し実装する方法はわかったので、トレイトを使っていろんな種類の型を受け付ける関数を定義する方法を学んでいきましょう。 たとえば、Listing 10-13では、NewsArticleとTweet型にSummaryトレイトを実装しました。 ここで、引数のitemのsummarizeメソッドを呼ぶ関数notifyを定義することができます。ただし、引数itemはSummaryトレイトを実装しているような何らかの型であるとします。 このようなことをするためには、impl Trait構文を使うことができます。 # pub trait Summary {\n# fn summarize(&self) -> String;\n# }\n# # pub struct NewsArticle {\n# pub headline: String,\n# pub location: String,\n# pub author: String,\n# pub content: String,\n# }\n# # impl Summary for NewsArticle {\n# fn summarize(&self) -> String {\n# format!(\"{}, by {} ({})\", self.headline, self.author, self.location)\n# }\n# }\n# # pub struct Tweet {\n# pub username: String,\n# pub content: String,\n# pub reply: bool,\n# pub retweet: bool,\n# }\n# # impl Summary for Tweet {\n# fn summarize(&self) -> String {\n# format!(\"{}: {}\", self.username, self.content)\n# }\n# }\n# pub fn notify(item: &impl Summary) { println!(\"Breaking news! {}\", item.summarize());\n} 引数のitemには、具体的な型の代わりに、implキーワードとトレイト名を指定します。 この引数は、指定されたトレイトを実装しているあらゆる型を受け付けます。 notifyの中身では、summarizeのような、Summaryトレイトに由来するitemのあらゆるメソッドを呼び出すことができます。 私達は、notifyを呼びだし、NewsArticleかTweetのどんなインスタンスでも渡すことができます。 この関数を呼び出すときに、Stringやi32のような他の型を渡すようなコードはコンパイルできません。 なぜなら、これらの型はSummaryを実装していないからです。 トレイト境界構文 impl Trait構文は単純なケースを解決しますが、実はより長い トレイト境界 (trait bound) と呼ばれる姿の糖衣構文 (syntax sugar) なのです。 それは以下のようなものです: pub fn notify(item: &T) { // 速報! {} println!(\"Breaking news! {}\", item.summarize());\n} この「より長い」姿は前節の例と等価ですが、より冗長です。 角カッコの中にジェネリックな型引数の宣言を書き、型引数の後ろにコロンを挟んでトレイト境界を置いています。 簡単なケースに対し、impl Trait構文は便利で、コードを簡潔にしてくれます。 そうでないケースの場合、トレイト境界構文を使えば複雑な状態を表現できます。 たとえば、Summaryを実装する2つのパラメータを持つような関数を考えることができます。 impl Trait構文を使うとこのようになるでしょう: pub fn notify(item1: &impl Summary, item2: &impl Summary) { この関数が受け取るitem1とitem2の型が(どちらもSummaryを実装する限り)異なっても良いとするならば、impl Traitは適切でしょう。 両方の引数が同じ型であることを強制することは、以下のようにトレイト境界を使ってのみ表現可能です: pub fn notify(item1: &T, item2: &T) { 引数であるitem1とitem2の型としてジェネリックな型Tを指定しました。 これにより、item1とitem2として関数に渡される値の具体的な型が同一でなければならない、という制約を与えています。 複数のトレイト境界を+構文で指定する 複数のトレイト境界も指定できます。 たとえば、notifyにsummarizeメソッドに加えてitemの画面出力形式(ディスプレイフォーマット)を使わせたいとします。 その場合は、notifyの定義にitemはDisplayとSummaryの両方を実装していなくてはならないと指定することになります。 これは、以下のように+構文で行うことができます: pub fn notify(item: &(impl Summary + Display)) { +構文はジェネリック型につけたトレイト境界に対しても使えます: pub fn notify(item: &T) { これら2つのトレイト境界が指定されていれば、notifyの中ではsummarizeを呼び出すことと、{}を使ってitemをフォーマットすることの両方が行なえます。 where句を使ったより明確なトレイト境界 あまりたくさんのトレイト境界を使うことには欠点もあります。 それぞれのジェネリック(な型)がそれぞれのトレイト境界をもつので、複数のジェネリック型の引数をもつ関数は、関数名と引数リストの間に大量のトレイト境界に関する情報を含むことがあります。 これでは関数のシグネチャが読みにくくなってしまいます。 このため、Rustはトレイト境界を関数シグネチャの後のwhere句の中で指定するという別の構文を用意しています。 なので、このように書く: fn some_function(t: &T, u: &U) -> i32 { 代わりに、where句を使い、このように書くことができます: fn some_function(t: &T, u: &U) -> i32 where T: Display + Clone, U: Clone + Debug\n{ この関数シグニチャは、よりさっぱりとしています。トレイト境界を多く持たない関数と同じように、関数名、引数リスト、戻り値の型が一緒になって近くにあるからですね。","breadcrumbs":"ジェネリック型、トレイト、ライフタイム » 引数としてのトレイト","id":"175","title":"引数としてのトレイト"},"176":{"body":"以下のように、impl Trait構文を戻り値型のところで使うことにより、あるトレイトを実装する何らかの型を返すことができます。 # pub trait Summary {\n# fn summarize(&self) -> String;\n# }\n# # pub struct NewsArticle {\n# pub headline: String,\n# pub location: String,\n# pub author: String,\n# pub content: String,\n# }\n# # impl Summary for NewsArticle {\n# fn summarize(&self) -> String {\n# format!(\"{}, by {} ({})\", self.headline, self.author, self.location)\n# }\n# }\n# # pub struct Tweet {\n# pub username: String,\n# pub content: String,\n# pub reply: bool,\n# pub retweet: bool,\n# }\n# # impl Summary for Tweet {\n# fn summarize(&self) -> String {\n# format!(\"{}: {}\", self.username, self.content)\n# }\n# }\n# fn returns_summarizable() -> impl Summary { Tweet { username: String::from(\"horse_ebooks\"), content: String::from( \"of course, as you probably already know, people\", ), reply: false, retweet: false, }\n} 戻り値の型としてimpl Summaryを使うことにより、具体的な型が何かを言うことなく、returns_summarizable関数はSummaryトレイトを実装している何らかの型を返すのだ、と指定することができます。 今回returns_summarizableはTweetを返しますが、この関数を呼び出すコードはそのことを知りません。 実装しているトレイトだけで戻り値型を指定できることは、13章で学ぶ、クロージャとイテレータを扱うときに特に便利です。 クロージャとイテレータの作り出す型は、コンパイラだけが知っているものであったり、指定するには長すぎるものであったりします。 impl Trait構文を使えば、非常に長い型を書くことなく、ある関数はIteratorトレイトを実装するある型を返すのだ、と簡潔に指定することができます。 ただし、impl Traitは一種類の型を返す場合にのみ使えます。 たとえば、以下のように、戻り値の型はimpl Summaryで指定しつつ、NewsArticleかTweetを返すようなコードは失敗します: # pub trait Summary {\n# fn summarize(&self) -> String;\n# }\n# # pub struct NewsArticle {\n# pub headline: String,\n# pub location: String,\n# pub author: String,\n# pub content: String,\n# }\n# # impl Summary for NewsArticle {\n# fn summarize(&self) -> String {\n# format!(\"{}, by {} ({})\", self.headline, self.author, self.location)\n# }\n# }\n# # pub struct Tweet {\n# pub username: String,\n# pub content: String,\n# pub reply: bool,\n# pub retweet: bool,\n# }\n# # impl Summary for Tweet {\n# fn summarize(&self) -> String {\n# format!(\"{}: {}\", self.username, self.content)\n# }\n# }\n# fn returns_summarizable(switch: bool) -> impl Summary { if switch { NewsArticle { headline: String::from( \"Penguins win the Stanley Cup Championship!\", ), location: String::from(\"Pittsburgh, PA, USA\"), author: String::from(\"Iceburgh\"), content: String::from( \"The Pittsburgh Penguins once again are the best \\ hockey team in the NHL.\", ), } } else { Tweet { username: String::from(\"horse_ebooks\"), content: String::from( \"of course, as you probably already know, people\", ), reply: false, retweet: false, } }\n} NewsArticleかTraitを返すというのは、コンパイラのimpl Trait構文の実装まわりの制約により許されていません。 このような振る舞いをする関数を書く方法は、17章の トレイトオブジェクトで異なる型の値を許容する 節で学びます。","breadcrumbs":"ジェネリック型、トレイト、ライフタイム » トレイトを実装している型を返す","id":"176","title":"トレイトを実装している型を返す"},"177":{"body":"ジェネリックな型引数の境界で使用したい振る舞いを指定する方法がわかったので、リスト10-5に戻って、 ジェネリックな型引数を使用するlargest関数の定義を修正しましょう!最後にそのコードを実行しようとした時、 こんなエラーが出ていました: $ cargo run Compiling chapter10 v0.1.0 (file:///projects/chapter10)\nerror[E0369]: binary operation `>` cannot be applied to type `T` --> src/main.rs:5:17 |\n5 | if item > largest { | ---- ^ ------- T | | | T | = note: `T` might need a bound for `std::cmp::PartialOrd` error: aborting due to previous error For more information about this error, try `rustc --explain E0369`.\nerror: could not compile `chapter10`. To learn more, run the command again with --verbose. largestの本体で、大なり演算子(>)を使用して型Tの2つの値を比較しようとしていました。この演算子は、 標準ライブラリトレイトのstd::cmp::PartialOrdでデフォルトメソッドとして定義されているので、 largest関数が、比較できるあらゆる型のスライスに対して動くようにするためには、Tのトレイト境界にPartialOrdを指定する必要があります。 PartialOrdはpreludeに含まれているので、これをスコープに導入する必要はありません。 largestのシグニチャを以下のように変えてください: fn largest(list: &[T]) -> T {\n# let mut largest = list[0];\n# # for &item in list {\n# if item > largest {\n# largest = item;\n# }\n# }\n# # largest\n# }\n# # fn main() {\n# let number_list = vec![34, 50, 25, 100, 65];\n# # let result = largest(&number_list);\n# println!(\"The largest number is {}\", result);\n# # let char_list = vec!['y', 'm', 'a', 'q'];\n# # let result = largest(&char_list);\n# println!(\"The largest char is {}\", result);\n# } 今回のコンパイルでは、別のエラーが出てきます: $ cargo run Compiling chapter10 v0.1.0 (file:///projects/chapter10)\nerror[E0508]: cannot move out of type `[T]`, a non-copy slice\n(エラー[E0508]: 型`[T]`をもつ、非コピーのスライスからのムーブはできません) --> src/main.rs:2:23 |\n2 | let mut largest = list[0]; | ^^^^^^^ | | | cannot move out of here | (ここからムーブすることはできません) | move occurs because `list[_]` has type `T`, which does not implement the `Copy` trait | (ムーブが発生するのは、`list[_]`は`T`という、`Copy`トレイトを実装しない型であるためです) | help: consider borrowing here: `&list[0]` | (助言:借用するようにしてみてはいかがですか: `&list[0]`) error[E0507]: cannot move out of a shared reference\n(エラー[E0507]:共有の参照からムーブはできません) --> src/main.rs:4:18 |\n4 | for &item in list { | ----- ^^^^ | || | |data moved here | |(データがここでムーブされています) | |move occurs because `item` has type `T`, which does not implement the `Copy` trait | |(ムーブが発生するのは、`item`は`T`という、`Copy`トレイトを実装しない型であるためです) | help: consider removing the `&`: `item` | (助言:`&`を取り除いてみてはいかがですか: `item`) error: aborting due to 2 previous errors Some errors have detailed explanations: E0507, E0508.\nFor more information about an error, try `rustc --explain E0507`.\nerror: could not compile `chapter10`. To learn more, run the command again with --verbose. このエラーの鍵となる行は、cannot move out of type [T], a non-copy sliceです。 ジェネリックでないバージョンのlargest関数では、最大のi32かcharを探そうとするだけでした。 第4章の スタックのみのデータ: コピー 節で議論したように、i32やcharのようなサイズが既知の型は スタックに格納できるので、Copyトレイトを実装しています。しかし、largest関数をジェネリックにすると、 list引数がCopyトレイトを実装しない型を含む可能性も出てきたのです。結果として、 list[0]から値をlargestにムーブできず、このエラーに陥ったのです。 このコードをCopyトレイトを実装する型だけを使って呼び出すようにしたいなら、Tのトレイト境界にCopyを追加すればよいです! リスト10-15は、関数に渡したスライスの値の型が、i32やcharなどのようにPartialOrd と Copyを実装する限りコンパイルできる、ジェネリックなlargest関数の完全なコードを示しています。 ファイル名: src/main.rs fn largest(list: &[T]) -> T { let mut largest = list[0]; for &item in list { if item > largest { largest = item; } } largest\n} fn main() { let number_list = vec![34, 50, 25, 100, 65]; let result = largest(&number_list); println!(\"The largest number is {}\", result); let char_list = vec!['y', 'm', 'a', 'q']; let result = largest(&char_list); println!(\"The largest char is {}\", result);\n} リスト10-15: PartialOrdとCopyトレイトを実装するあらゆるジェネリックな型に対して動く、 largest関数の実際の定義 もしlargest関数をCopyを実装する型だけに制限したくなかったら、TがCopyではなくCloneというトレイト境界を持つと指定することもできます。そうしたら、 largest関数に所有権が欲しい時にスライスの各値をクローンできます。clone関数を使用するということは、 Stringのようなヒープデータを持つ型の場合により多くのヒープ確保が発生する可能性があることを意味します。 そして、大量のデータを取り扱っていたら、ヒープ確保には時間がかかることもあります。 largestの別の実装方法は、関数がスライスのT値への参照を返すようにすることです。 戻り値の型をTではなく&Tに変え、それにより関数の本体を参照を返すように変更したら、 CloneやCopyトレイト境界は必要なくなり、ヒープ確保も避けられるでしょう。 これらの代替策をご自身で実装してみましょう!","breadcrumbs":"ジェネリック型、トレイト、ライフタイム » トレイト境界でlargest関数を修正する","id":"177","title":"トレイト境界でlargest関数を修正する"},"178":{"body":"ジェネリックな型引数を持つimplブロックにトレイト境界を与えることで、 特定のトレイトを実装する型に対するメソッド実装を条件分けできます。例えば、 リスト10-16の型Pairは、常にnew関数を実装します。しかし、Pairは、 内部の型Tが比較を可能にするPartialOrdトレイト と 出力を可能にするDisplayトレイトを実装している時のみ、 cmp_displayメソッドを実装します。 ファイル名: src/lib.rs use std::fmt::Display; struct Pair { x: T, y: T,\n} impl Pair { fn new(x: T, y: T) -> Self { Self { x, y } }\n} impl Pair { fn cmp_display(&self) { if self.x >= self.y { println!(\"The largest member is x = {}\", self.x); } else { println!(\"The largest member is y = {}\", self.y); } }\n} リスト10-16: トレイト境界によってジェネリックな型に対するメソッド実装を条件分けする また、別のトレイトを実装するあらゆる型に対するトレイト実装を条件分けすることもできます。 トレイト境界を満たすあらゆる型にトレイトを実装することは、 ブランケット実装 (blanket implementation)と呼ばれ、 Rustの標準ライブラリで広く使用されています。例を挙げれば、標準ライブラリは、 Displayトレイトを実装するあらゆる型にToStringトレイトを実装しています。 標準ライブラリのimplブロックは以下のような見た目です: impl ToString for T { // --snip--\n} 標準ライブラリにはこのブランケット実装があるので、Displayトレイトを実装する任意の型に対して、 ToStringトレイトで定義されたto_stringメソッドを呼び出せるのです。 例えば、整数はDisplayを実装するので、このように整数値を対応するString値に変換できます: let s = 3.to_string(); ブランケット実装は、トレイトのドキュメンテーションの「実装したもの」節に出現します。 トレイトとトレイト境界により、ジェネリックな型引数を使用して重複を減らしつつ、コンパイラに対して、 そのジェネリックな型に特定の振る舞いが欲しいことを指定するコードを書くことができます。 それからコンパイラは、トレイト境界の情報を活用してコードに使用された具体的な型が正しい振る舞いを提供しているか確認できます。 動的型付き言語では、その型に定義されていないメソッドを呼び出せば、実行時 (runtime) にエラーが出るでしょう。 しかし、Rustはこの種のエラーをコンパイル時に移したので、コードが動かせるようになる以前に問題を修正することを強制されるのです。 加えて、コンパイル時に既に確認したので、実行時の振る舞いを確認するコードを書かなくても済みます。 そうすることで、ジェネリクスの柔軟性を諦めることなくパフォーマンスを向上させます。 すでに使っている他のジェネリクスに、ライフタイムと呼ばれるものがあります。 ライフタイムは、型が欲しい振る舞いを保持していることではなく、必要な間だけ参照が有効であることを保証します。 ライフタイムがどうやってそれを行うかを見てみましょう。","breadcrumbs":"ジェネリック型、トレイト、ライフタイム » トレイト境界を使用して、メソッド実装を条件分けする","id":"178","title":"トレイト境界を使用して、メソッド実装を条件分けする"},"179":{"body":"第4章の「参照と借用」節で議論しなかった詳細の一つに、Rustにおいて参照は全てライフタイムを保持するということがあります。 ライフタイムとは、その参照が有効になるスコープのことです。多くの場合、型が推論されるように、 大体の場合、ライフタイムも暗黙的に推論されます。複数の型の可能性があるときには、型を注釈しなければなりません。 同様に、参照のライフタイムがいくつか異なる方法で関係することがある場合には注釈しなければなりません。 コンパイラは、ジェネリックライフタイム引数を使用して関係を注釈し、実行時に実際の参照が確かに有効であることを保証することを要求するのです。 ライフタイムの概念は、他のプログラミング言語の道具とはどこか異なり、間違いなく、 Rustで一番際立った機能になっています。この章では、ライフタイムの全てを講義しないものの、 ライフタイム記法と遭遇する可能性のある一般的な手段を議論するので、その概念に馴染めます。 もっと詳しく知るには、第19章の「高度なライフタイム」節を参照されたし。","breadcrumbs":"ジェネリック型、トレイト、ライフタイム » ライフタイムで参照を検証する","id":"179","title":"ライフタイムで参照を検証する"},"18":{"body":"Rustが正常にインストールされているか確かめるには、シェルを開いて以下の行を入力してください: $ rustc --version バージョンナンバー、コミットハッシュ、最新の安定版がリリースされたコミット日時が以下のフォーマットで表示されるのを目撃するはずです。 rustc x.y.z (abcabcabc yyyy-mm-dd) この情報が見れたら、Rustのインストールに成功しました!この情報が出ず、Windowsを使っているなら、 Rustが%PATH%システム環境変数にあることを確認してください。全て正常で、それでもRustが動かないなら、 助力を得られる場所はたくさんあります。最も簡単なのが irc.mozilla.orgの#rust IRCチャンネル で、 Mibbit を通してアクセスできます。そのアドレスで、助けてくれる他のRustacean(自分たちを呼ぶバカなニックネーム)とチャットできます。 他の素晴らしいリソースには、 ユーザ・フォーラム と Stack Overflow が含まれます。 Rustacean: いらないかもしれない補足です。Rustaceanは公式にcrustaceans(甲殻類)から 来て いるそうです。 そのため、Rustのマスコットは非公式らしいですが、 カニ 。上の会話でCの欠点を削ぎ落としているからcを省いてるの?みたいなことを聞いていますが、 違うそうです。検索したら、堅牢性が高いから甲殻類という意見もありますが、真偽は不明です。 明日使えるかもしれないトリビアでした。","breadcrumbs":"事始め » トラブルシューティング","id":"18","title":"トラブルシューティング"},"180":{"body":"ライフタイムの主な目的は、ダングリング参照を回避することです。ダングリング参照によりプログラムは、 参照するつもりだったデータ以外のデータを参照してしまいます。リスト10-17のプログラムを考えてください。 これには、外側のスコープと内側のスコープが含まれています。 { let r; { let x = 5; r = &x; } println!(\"r: {}\", r);\n} リスト10-17: 値がスコープを抜けてしまった参照を使用しようとする 注釈: リスト10-17や10-18、10-24では、変数に初期値を与えずに宣言しているので、変数名は外側のスコープに存在します。 初見では、これはRustにはnull値が存在しないということと衝突しているように見えるかもしれません。 しかしながら、値を与える前に変数を使用しようとすれば、コンパイルエラーになり、 これは、確かにRustではnull値は許可されないことを示します。 外側のスコープで初期値なしのrという変数を宣言し、内側のスコープで初期値5のxという変数を宣言しています。 内側のスコープ内で、rの値をxへの参照にセットしようとしています。それから内側のスコープが終わり、 rの値を出力しようとしています。rが参照している値が使おうとする前にスコープを抜けるので、 このコードはコンパイルできません。こちらがエラーメッセージです: error[E0597]: `x` does not live long enough\n(エラー: `x`の生存期間が短すぎます) --> src/main.rs:7:5 |\n6 | r = &x; | - borrow occurs here | (借用はここで起きています)\n7 | } | ^ `x` dropped here while still borrowed | (`x`は借用されている間にここでドロップされました)\n...\n10 | } | - borrowed value needs to live until here | (借用された値はここまで生きる必要があります) 変数xの「生存期間が短すぎます」。原因は、内側のスコープが7行目で終わった時点でxがスコープを抜けるからです。 ですが、rはまだ、外側のスコープに対して有効です; スコープが大きいので、「長生きする」と言います。 Rustで、このコードが動くことを許可していたら、rはxがスコープを抜けた時に解放されるメモリを参照していることになり、 rで行おうとするいかなることもちゃんと動作しないでしょう。では、どうやってコンパイラはこのコードが無効であると決定しているのでしょうか? 借用チェッカーを使用しています。","breadcrumbs":"ジェネリック型、トレイト、ライフタイム » ライフタイムでダングリング参照を回避する","id":"180","title":"ライフタイムでダングリング参照を回避する"},"181":{"body":"Rustコンパイラには、スコープを比較して全ての借用が有効であるかを決定する 借用チェッカー があります。 リスト10-18は、リスト10-17と同じコードを示していますが、変数のライフタイムを表示する注釈が付いています: { let r; // ---------+-- 'a // | { // | let x = 5; // -+-- 'b | r = &x; // | | } // -+ | // | println!(\"r: {}\", r); // |\n} // ---------+ リスト10-18: それぞれ'aと'bと名付けられたrとxのライフタイムの注釈 ここで、rのライフタイムは'a、xのライフタイムは'bで注釈しました。ご覧の通り、 内側の'bブロックの方が、外側の'aライフタイムブロックよりはるかに小さいです。 コンパイル時に、コンパイラは2つのライフタイムのサイズを比較し、rは'aのライフタイムだけれども、 'bのライフタイムのメモリを参照していると確認します。'bは'aよりも短いので、プログラムは拒否されます: 参照の対象が参照ほど長生きしないのです。 リスト10-19でコードを修正したので、ダングリング参照はなくなり、エラーなくコンパイルできます。 { let x = 5; // ----------+-- 'b // | let r = &x; // --+-- 'a | // | | println!(\"r: {}\", r); // | | // --+ |\n} // ----------+ リスト10-19: データのライフタイムが参照より長いので、有効な参照 ここでxのライフタイムは'bになり、今回の場合'aよりも大きいです。つまり、 コンパイラはxが有効な間、rの参照も常に有効になることを把握しているので、rはxを参照できます。 今や、参照のライフタイムがどれだけあり、コンパイラがライフタイムを解析して参照が常に有効であることを保証する仕組みがわかったので、 関数の文脈でジェネリックな引数と戻り値のライフタイムを探究しましょう。","breadcrumbs":"ジェネリック型、トレイト、ライフタイム » 借用精査機","id":"181","title":"借用精査機"},"182":{"body":"2つの文字列スライスのうち、長い方を返す関数を書きましょう。この関数は、 2つの文字列スライスを取り、1つの文字列スライスを返します。longest関数の実装完了後、 リスト10-20のコードは、The logest string is abcdと出力するはずです。 ファイル名: src/main.rs fn main() { let string1 = String::from(\"abcd\"); let string2 = \"xyz\"; let result = longest(string1.as_str(), string2); // 最長の文字列は、{}です println!(\"The longest string is {}\", result);\n} リスト10-20: longest関数を呼び出して2つの文字列スライスのうち長い方を探すmain関数 関数に取ってほしい引数が文字列スライス、つまり参照であることに注意してください。 何故なら、longest関数に引数の所有権を奪ってほしくないからです。 この関数にStringのスライス(変数string1に格納されている型)と文字列リテラル(変数string2が含むもの)を受け取らせたいのです。 リスト10-20で使用している引数が、我々が必要としているものである理由についてもっと詳しい議論は、 第4章の「引数としての文字列スライス」節をご参照ください。 リスト10-21に示したようにlongest関数を実装しようとしたら、コンパイルできないでしょう。 ファイル名: src/main.rs fn longest(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y }\n} リスト10-21: 2つの文字列スライスのうち長い方を返すけれども、コンパイルできないlongest関数の実装 代わりに、以下のようなライフタイムに言及するエラーが出ます: error[E0106]: missing lifetime specifier\n(エラー: ライフタイム指定子が不足しています) --> src/main.rs:1:33 |\n1 | fn longest(x: &str, y: &str) -> &str { | ^ expected lifetime parameter | (ライフタイム引数が予想されます) | = help: this function's return type contains a borrowed value, but the\nsignature does not say whether it is borrowed from `x` or `y` (助言: この関数の戻り値型は借用された値を含んでいますが、\nシグニチャは、それが`x`か`y`由来のものなのか宣言していません) 助言テキストが、戻り値の型はジェネリックなライフタイム引数である必要があると明かしています。 というのも、返している参照がxかyのどちらを参照しているか、コンパイラにはわからないからです。 この関数の本体のifブロックはxへの参照を返し、elseブロックはyへの参照を返すので、 実際、どちらか私たちにもわかりません! この関数を定義する際、この関数に渡される具体的な値がわからないので、ifケースか、elseケースが実行されるか、わからないのです。 また、渡される参照の具体的なライフタイムもわからないので、リスト10-18と10-19で、 返す参照が常に有効であるかを決定したように、スコープを見ることもできないのです。 借用チェッカーもこれを決定することはできません。xとyのライフタイムがどう戻り値のライフタイムと関係するかわからないからです。 このエラーを修正するには、借用チェッカーが解析を実行できるように、参照間の関係を定義するジェネリックなライフタイム引数を追加します。","breadcrumbs":"ジェネリック型、トレイト、ライフタイム » 関数のジェネリックなライフタイム","id":"182","title":"関数のジェネリックなライフタイム"},"183":{"body":"ライフタイム注釈は、いかなる参照の生存期間も変えることはありません。シグニチャがジェネリックな型引数を指定していると、 関数があらゆる型を受け入れるのと全く同様に、ジェネリックなライフタイム引数を指定することで関数は、 あらゆるライフタイムの参照を受け入れるのです。ライフタイム注釈は、ライフタイムに影響することなく、 複数の参照のライフタイムのお互いの関係を記述します。 ライフタイム注釈は、少し不自然な記法です: ライフタイム引数の名前はアポストロフィー(')で始まらなければならず、 通常全部小文字で、ジェネリック型のようにとても短いです。多くの人は、'aという名前を使います。 ライフタイム引数注釈は、参照の&の後に配置し、注釈と参照の型を区別するために空白を1つ使用します。 例を挙げましょう: ライフタイム引数なしのi32への参照、'aというライフタイム引数付きのi32への参照、 そしてこれもライフタイム'a付きのi32への可変参照です。 &i32 // a reference // (ただの)参照\n&'a i32 // a reference with an explicit lifetime // 明示的なライフタイム付きの参照\n&'a mut i32 // a mutable reference with an explicit lifetime // 明示的なライフタイム付きの可変参照 1つのライフタイム注釈それだけでは、大して意味はありません。注釈は、複数の参照のジェネリックなライフタイム引数が、 お互いにどう関係するかをコンパイラに指示することを意図しているからです。例えば、 ライフタイム'a付きのi32への参照となる引数firstのある関数があるとしましょう。 この関数にはさらに、'aのライフタイム付きのi32への別の参照となるsecondという別の引数もあります。 ライフタイム注釈は、firstとsecondの参照がどちらもジェネリックなライフタイムと同じだけ生きることを示唆します。","breadcrumbs":"ジェネリック型、トレイト、ライフタイム » ライフタイム注釈記法","id":"183","title":"ライフタイム注釈記法"},"184":{"body":"さて、longest関数の文脈でライフタイム注釈を調査しましょう。ジェネリックな型引数同様、 関数名と引数リストの間、山カッコの中にジェネリックなライフタイム引数を宣言する必要があります。 このシグニチャで表現したい制約は、引数の全参照と戻り値が同じライフタイムになることです。 ライフタイムを'aと名付け、それから各参照に追記します。リスト10-22に示したように。 ファイル名: src/main.rs fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y }\n} リスト10-22: シグニチャの全参照が同じライフタイム'aになると指定したlongest関数の定義 このコードはコンパイルでき、リスト10-20のmain関数とともに使用したら、欲しい結果になるはずです。 これで関数シグニチャは、何らかのライフタイム'aに対して、関数は2つの引数を取り、 どちらも少なくともライフタイム'aと同じだけ生きる文字列スライスであるとコンパイラに教えるようになりました。 また、この関数シグニチャは、関数から返る文字列スライスも少なくともライフタイム'aと同じだけ生きると、 コンパイラに教えています。これらの制約は、コンパイラに強制してほしいものです。 この関数シグニチャでライフタイム引数を指定する時、渡されたり、返したりした、いかなる値のライフタイムも変更していないことを思い出してください。 むしろ、借用チェッカーは、これらの制約を守らない値全てを拒否するべきと指定しています。 longest関数は、正確にxとyの生存期間を知る必要はなく、何かのスコープが'aに代替され、 このシグニチャを満足することだけ知っている必要があることに注意してください。 関数でライフタイムを注釈する際、注釈は関数シグニチャに嵌(はま)り、 関数本体には嵌りません。コンパイラは、なんの助けもなく、関数内のコードを解析できます。しかしながら、 関数に、関数外からの参照や、関数外への参照がある場合、コンパイラは引数や戻り値のライフタイムをそれだけで解決することはほとんど不可能になります。 ライフタイムは、関数が呼び出される度に異なる可能性があります。このために、手動でライフタイムを注釈する必要があるのです。 具体的な参照をlongestに渡すと、'aを代替する具体的なライフタイムは、yのスコープと被さるxのスコープの一部になります。 言い換えると、ジェネリックなライフタイム'aは、xとyのライフタイムのうち、小さい方に等しい具体的なライフタイムになるのです。 返却される参照を同じライフタイム引数'aで注釈したので、返却される参照もxかyのライフタイムの小さい方と同じだけ有効になるでしょう。 ライフタイム注釈が異なる具体的なライフタイムになる参照を渡すことでlongest関数を制限する方法を見ましょう。 リスト10-23は、率直な例です。 ファイル名: src/main.rs # fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {\n# if x.len() > y.len() {\n# x\n# } else {\n# y\n# }\n# }\n#\nfn main() { // 長い文字列は長い let string1 = String::from(\"long string is long\"); { let string2 = String::from(\"xyz\"); let result = longest(string1.as_str(), string2.as_str()); println!(\"The longest string is {}\", result); }\n} リスト10-23: 異なる具体的なライフタイムのString値への参照でlongest関数を使用する この例において、string1は外側のスコープの終わりまで有効で、string2は内側のスコープの終わりまで有効、 そしてresultは内側のスコープの終わりまで有効な何かを参照しています。このコードを実行すると、 借用チェッカーがこのコードを良しとするのがわかるでしょう。要するに、コンパイルでき、 The longest string is long string is longと出力するのです。 次に、resultの参照のライフタイムが2つの引数の小さい方のライフタイムになることを示す例を試しましょう。 result変数の宣言を内側のスコープの外に移すものの、result変数への代入はstring2のスコープ内に残したままにします。 それからresultを使用するprintln!を内側のスコープの外、内側のスコープが終わった後に移動します。 リスト10-24のコードはコンパイルできません。 ファイル名: src/main.rs fn main() { let string1 = String::from(\"long string is long\"); let result; { let string2 = String::from(\"xyz\"); result = longest(string1.as_str(), string2.as_str()); } println!(\"The longest string is {}\", result);\n} リスト10-24: string2がスコープを抜けてからresultを使用しようとする このコードのコンパイルを試みると、こんなエラーになります: error[E0597]: `string2` does not live long enough --> src/main.rs:15:5 |\n14 | result = longest(string1.as_str(), string2.as_str()); | ------- borrow occurs here\n15 | } | ^ `string2` dropped here while still borrowed\n16 | println!(\"The longest string is {}\", result);\n17 | } | - borrowed value needs to live until here このエラーは、resultがprintln!文に対して有効になるために、string2が外側のスコープの終わりまで有効である必要があることを示しています。 関数引数と戻り値のライフタイムを同じライフタイム引数'aで注釈したので、コンパイラはこのことを知っています。 人間からしたら、このコードを見てstring1はstring2よりも長いことが確認でき、 故にresultはstring1への参照を含んでいます。まだstring1はスコープを抜けていないので、 それでもstring1への参照はprintln!にとって有効でしょう。ですが、コンパイラはこの場合、 参照が有効であると見なせません。longest関数から返ってくる参照のライフタイムは、 渡した参照のうちの小さい方と同じだとコンパイラに指示しました。それ故に、 借用チェッカーは、リスト10-24のコードを無効な参照がある可能性があるとして許可しないのです。 試しに値や、longest関数に渡される参照のライフタイムや、返される参照の使用法が異なる実験をもっとしてみてください。 自分の実験がコンパイル前に借用チェッカーを通るかどうか仮説を立ててください; そして、正しいか確かめてください!","breadcrumbs":"ジェネリック型、トレイト、ライフタイム » 関数シグニチャにおけるライフタイム注釈","id":"184","title":"関数シグニチャにおけるライフタイム注釈"},"185":{"body":"ライフタイム引数を指定する必要のある手段は、関数が行っていることによります。例えば、 longest関数の実装を最長の文字列スライスではなく、常に最初の引数を返すように変更したら、 y引数に対してライフタイムを指定する必要はなくなるでしょう。以下のコードはコンパイルできます: ファイル名: src/main.rs fn longest<'a>(x: &'a str, y: &str) -> &'a str { x\n} この例では、引数xと戻り値に対してライフタイム引数'aを指定しましたが、引数yには指定していません。 yのライフタイムはxや戻り値のライフタイムとは何の関係もないからです。 関数から参照を返す際、戻り値型のライフタイム引数は、引数のうちどれかのライフタイム引数と一致する必要があります。 返される参照が引数のどれかを参照してい なけれ ば、この関数内で生成された値を参照しているに違いなく、 これは、その値が関数の末端でスコープを抜けるので、ダングリング参照になるでしょう。 コンパイルできないこのlongest関数の未遂の実装を考えてください: ファイル名: src/main.rs fn longest<'a>(x: &str, y: &str) -> &'a str { // 本当に長い文字列 let result = String::from(\"really long string\"); result.as_str()\n} ここでは、たとえ、戻り値型にライフタイム引数'aを指定していても、戻り値のライフタイムは、 引数のライフタイムと全く関係がないので、この実装はコンパイルできないでしょう。 こちらが、得られるエラーメッセージです: error[E0597]: `result` does not live long enough --> src/main.rs:3:5 |\n3 | result.as_str() | ^^^^^^ does not live long enough\n4 | } | - borrowed value only lives until here |\nnote: borrowed value must be valid for the lifetime 'a as defined on the\nfunction body at 1:1...\n(注釈: 借用された値は、関数本体1行目1文字目で定義されているようにライフタイム'aに対して有効でなければなりません) --> src/main.rs:1:1 |\n1 | / fn longest<'a>(x: &str, y: &str) -> &'a str {\n2 | | let result = String::from(\"really long string\");\n3 | | result.as_str()\n4 | | } | |_^ 問題は、resultがlongest関数の末端でスコープを抜け、片付けられてしまうことです。 また、関数からresultを返そうともしています。ダングリング参照を変えるであろうライフタイム引数を指定する手段はなく、 コンパイラは、ダングリング参照を生成させてくれません。今回の場合、最善の修正案は、 呼び出し元の関数が値の片付けに責任を持てるよう、参照ではなく所有されたデータ型を返すことでしょう。 究極的にライフタイム記法は、関数のいろんな引数と戻り値のライフタイムを接続することに関するのです。 一旦、繋がりができたら、メモリ安全な処理を許可するのに十分な情報がコンパイラにはあり、 ダングリングポインタを生成するであろう処理を不認可し、さもなくばメモリ安全性を侵害するのです。","breadcrumbs":"ジェネリック型、トレイト、ライフタイム » ライフタイムの観点で思考する","id":"185","title":"ライフタイムの観点で思考する"},"186":{"body":"ここまで、所有された型を保持する構造体だけを定義してきました。構造体に参照を保持させることもできますが、 その場合、構造体定義の全参照にライフタイム注釈を付け加える必要があるでしょう。 リスト10-25には、文字列スライスを保持するImportantExcerpt(重要な一節)という構造体があります。 ファイル名: src/main.rs struct ImportantExcerpt<'a> { part: &'a str,\n} fn main() { // 僕をイシュマエルとお呼び。何年か前・・・ let novel = String::from(\"Call me Ishmael. Some years ago...\"); let first_sentence = novel.split('.') .next() .expect(\"Could not find a '.'\"); // '.'が見つかりませんでした let i = ImportantExcerpt { part: first_sentence };\n} リスト10-25: 参照を含む構造体なので、定義にライフタイム注釈が必要 この構造体には文字列スライスを保持する1つのフィールド、partがあり、これは参照です。 ジェネリックなデータ型同様、構造体名の後、山カッコの中にジェネリックなライフタイム引数の名前を宣言するので、 構造体定義の本体でライフタイム引数を使用できます。この注釈は、ImportantExcerptのインスタンスが、 partフィールドに保持している参照よりも長生きしないことを意味します。 ここのmain関数は、変数novelに所有されるStringの、最初の文への参照を保持するImportantExcerptインスタンスを生成しています。 novelのデータは、ImportantExcerptインスタンスが作られる前に存在しています。 加えて、ImportantExcerptがスコープを抜けるまでnovelはスコープを抜けないので、 ImportantExcerptインスタンスの参照は有効なのです。","breadcrumbs":"ジェネリック型、トレイト、ライフタイム » 構造体定義のライフタイム注釈","id":"186","title":"構造体定義のライフタイム注釈"},"187":{"body":"全参照にはライフタイムがあり、参照を使用する関数や構造体にはライフタイム引数を指定する必要があることを学びました。 ですが、リスト4-9にとある関数があり、リスト10-26に再度示しましたが、 これは、ライフタイム注釈なしでコンパイルできました。 ファイル名: src/lib.rs fn first_word(s: &str) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..]\n} リスト10-26: 引数と戻り値型が参照であるにも関わらず、ライフタイム注釈なしでコンパイルできた リスト4-9で定義した関数 この関数がライフタイム注釈なしでコンパイルできた理由は、歴史的なものです: 昔のバージョンのRust(1.0以前)では、 全参照に明示的なライフタイムが必要だったので、このコードはコンパイルできませんでした。 その頃、関数シグニチャはこのように記述されていたのです: fn first_word<'a>(s: &'a str) -> &'a str { 多くのRustコードを書いた後、Rustチームは、Rustプログラマが特定の場面では、 何度も何度も同じライフタイム注釈を入力することを発見しました。これらの場面は予測可能で、 いくつかの決定的なパターンに従っていました。開発者はこのパターンをコンパイラのコードに落とし込んだので、 このような場面には借用チェッカーがライフタイムを推論できるようになり、明示的な注釈を必要としなくなったのです。 他の決定的なパターンが出現し、コンパイラに追加されることもあり得るので、このRustの歴史は関係があります。 将来的に、さらに少数のライフタイム注釈しか必要にならない可能性もあります。 コンパイラの参照解析に落とし込まれたパターンは、 ライフタイム省略規則 と呼ばれます。 これらはプログラマが従う規則ではありません; コンパイラが考慮する一連の特定のケースであり、 自分のコードがこのケースに当てはまれば、ライフタイムを明示的に書く必要はなくなります。 省略規則は、完全な推論を提供しません。コンパイラが決定的に規則を適用できるけれども、 参照が保持するライフタイムに関してそれでも曖昧性があるなら、コンパイラは、残りの参照がなるべきライフタイムを推測しません。 この場合コンパイラは、それらを推測するのではなくエラーを与えます。 これらは、参照がお互いにどう関係するかを指定するライフタイム注釈を追記することで解決できます。 関数やメソッドの引数のライフタイムは、 入力ライフタイム と呼ばれ、 戻り値のライフタイムは 出力ライフタイム と称されます。 コンパイラは3つの規則を活用し、明示的な注釈がない時に、参照がどんなライフタイムになるかを計算します。 最初の規則は入力ライフタイムに適用され、2番目と3番目の規則は出力ライフタイムに適用されます。 コンパイラが3つの規則の最後まで到達し、それでもライフタイムを割り出せない参照があったら、 コンパイラはエラーで停止します。 最初の規則は、参照である各引数は、独自のライフタイム引数を得るというものです。換言すれば、 1引数の関数は、1つのライフタイム引数を得るということです: fn foo<'a>(x: &'a i32); 2つ引数のある関数は、2つの個別のライフタイム引数を得ます: fn foo<'a, 'b>(x: &'a i32, y: &'b i32); 以下同様。 2番目の規則は、1つだけ入力ライフタイム引数があるなら、そのライフタイムが全ての出力ライフタイム引数に代入されるというものです: fn foo<'a>(x: &'a i32) -> &'a i32。 3番目の規則は、複数の入力ライフタイム引数があるけれども、メソッドなのでそのうちの一つが&selfや&mut selfだったら、 selfのライフタイムが全出力ライフタイム引数に代入されるというものです。 この3番目の規則により、必要なシンボルの数が減るので、メソッドが遥かに読み書きしやすくなります。 コンパイラになってみましょう。これらの規則を適用して、リスト10-26のfirst_word関数のシグニチャの参照のライフタイムが何か計算します。 シグニチャは、参照に紐づけられるライフタイムがない状態から始まります: fn first_word(s: &str) -> &str { そうして、コンパイラは最初の規則を適用し、各引数が独自のライフタイムを得ると指定します。 それを通常通り'aと呼ぶので、シグニチャはこうなります: fn first_word<'a>(s: &'a str) -> &str { 1つだけ入力ライフタイムがあるので、2番目の規則を適用します。2番目の規則は、1つの入力引数のライフタイムが、 出力引数に代入されると指定するので、シグニチャはこうなります: fn first_word<'a>(s: &'a str) -> &'a str { もうこの関数シグニチャの全ての参照にライフタイムが付いたので、コンパイラは、 プログラマにこの関数シグニチャのライフタイムを注釈してもらう必要なく、解析を続行できます。 別の例に目を向けましょう。今回は、リスト10-21で取り掛かったときにはライフタイム引数がなかったlongest関数です: fn longest(x: &str, y: &str) -> &str { 最初の規則を適用しましょう: 各引数が独自のライフタイムを得るのです。今回は、 1つではなく2つ引数があるので、ライフタイムも2つです: fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str { 2つ以上入力ライフタイムがあるので、2番目の規則は適用されないとわかります。また3番目の規則も適用されません。 longestはメソッドではなく関数なので、どの引数もselfではないのです。3つの規則全部を適用した後、 まだ戻り値型のライフタイムが判明していません。このために、リスト10-21でこのコードをコンパイルしようとしてエラーになったのです: コンパイラは、ライフタイム省略規則全てを適用したけれども、シグニチャの参照全部のライフタイムを計算できなかったのです。 3番目の規則は本当にメソッドシグニチャでしか適用されないので、次にその文脈でライフタイムを観察し、 3番目の規則が、メソッドシグニチャであまり頻繁にライフタイムを注釈しなくても済むことを意味する理由を確認します。","breadcrumbs":"ジェネリック型、トレイト、ライフタイム » ライフタイム省略","id":"187","title":"ライフタイム省略"},"188":{"body":"構造体にライフタイムのあるメソッドを実装する際、リスト10-11で示したジェネリックな型引数と同じ記法を使用します。 ライフタイム引数を宣言し使用する場所は、構造体フィールドかメソッド引数と戻り値に関係するかによります。 構造体のフィールド用のライフタイム名は、implキーワードの後に宣言する必要があり、 それから構造体名の後に使用されます。そのようなライフタイムは構造体の型の一部になるからです。 implブロック内のメソッドシグニチャでは、参照は構造体のフィールドの参照のライフタイムに紐づくか、 独立している可能性があります。加えて、ライフタイム省略規則により、メソッドシグニチャでライフタイム注釈が必要なくなることがよくあります。 リスト10-25で定義したImportantExcerptという構造体を使用して、何か例を見ましょう。 まず、唯一の引数がselfへの参照で戻り値がi32という何かへの参照ではないlevelというメソッドを使用します: # struct ImportantExcerpt<'a> {\n# part: &'a str,\n# }\n#\nimpl<'a> ImportantExcerpt<'a> { fn level(&self) -> i32 { 3 }\n} impl後のライフタイム引数宣言と型名の後に使用するのは必須ですが、最初の省略規則のため、 selfへの参照のライフタイムを注釈する必要はありません。 3番目のライフタイム省略規則が適用される例はこちらです: # struct ImportantExcerpt<'a> {\n# part: &'a str,\n# }\n#\nimpl<'a> ImportantExcerpt<'a> { fn announce_and_return_part(&self, announcement: &str) -> &str { // お知らせします println!(\"Attention please: {}\", announcement); self.part }\n} 2つ入力ライフタイムがあるので、コンパイラは最初のライフタイム省略規則を適用し、 &selfとannouncementに独自のライフタイムを与えます。それから、 引数の1つが&selfなので、戻り値型は&selfのライフタイムを得て、 全てのライフタイムが説明されました。","breadcrumbs":"ジェネリック型、トレイト、ライフタイム » メソッド定義におけるライフタイム注釈","id":"188","title":"メソッド定義におけるライフタイム注釈"},"189":{"body":"議論する必要のある1種の特殊なライフタイムが、'staticであり、これはプログラム全体の期間を示します。 文字列リテラルは全て'staticライフタイムになり、次のように注釈できます: // 静的ライフタイムを持ってるよ\nlet s: &'static str = \"I have a static lifetime.\"; この文字列のテキストは、プログラムのバイナリに直接格納され、常に利用可能です。故に、全文字列リテラルのライフタイムは、 'staticなのです。 エラーメッセージで'staticライフタイムを使用する提言を目撃する可能性があります。 ですが、参照に対してライフタイムとして'staticを指定する前に、今ある参照が本当にプログラムの全期間生きるかどうか考えてください。 可能であっても、参照がそれだけの期間生きてほしいかどうか考慮する可能性があります。 ほとんどの場合、問題は、ダングリング参照を生成しようとしているか、利用可能なライフタイムの不一致が原因です。 そのような場合、解決策はその問題を修正することであり、'staticライフタイムを指定することではありません。","breadcrumbs":"ジェネリック型、トレイト、ライフタイム » 静的ライフタイム","id":"189","title":"静的ライフタイム"},"19":{"body":"インストーラは、ドキュメンテーションの複製もローカルに含んでいるので、オフラインで閲覧することができます。 ブラウザでローカルのドキュメンテーションを開くには、rustup docを実行してください。 標準ライブラリにより型や関数が提供され、それがなんなのかや使用方法に確信が持てない度に、APIドキュメンテーションを使用して探してください!","breadcrumbs":"事始め » ローカルのドキュメンテーション","id":"19","title":"ローカルのドキュメンテーション"},"190":{"body":"ジェネリックな型引数、トレイト境界、ライフタイムを指定する記法を全て1関数でちょっと眺めましょう! use std::fmt::Display; fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str where T: Display\n{ // アナウンス! println!(\"Announcement! {}\", ann); if x.len() > y.len() { x } else { y }\n} これがリスト10-22からの2つの文字列のうち長い方を返すlongest関数ですが、 ジェネリックな型Tのannという追加の引数があり、これはwhere節で指定されているように、 Displayトレイトを実装するあらゆる型で埋めることができます。 この追加の引数は、関数が文字列スライスの長さを比較する前に出力されるので、 Displayトレイト境界が必要なのです。ライフタイムは一種のジェネリックなので、 ライフタイム引数'aとジェネリックな型引数Tが関数名の後、山カッコ内の同じリストに収まっています。","breadcrumbs":"ジェネリック型、トレイト、ライフタイム » ジェネリックな型引数、トレイト境界、ライフタイムを一度に","id":"190","title":"ジェネリックな型引数、トレイト境界、ライフタイムを一度に"},"191":{"body":"いろんなことをこの章では講義しましたね!今やジェネリックな型引数、トレイトとトレイト境界、そしてジェネリックなライフタイム引数を知ったので、 多くの異なる場面で動くコードを繰り返しなく書く準備ができました。ジェネリックな型引数により、 コードを異なる型に適用させてくれます。トレイトとトレイト境界は、型がジェネリックであっても、 コードが必要とする振る舞いを持つことを保証します。ライフタイム注釈を活用して、 この柔軟なコードにダングリング参照が存在しないことを保証する方法を学びました。 さらにこの解析は全てコンパイル時に起こり、実行時のパフォーマンスには影響しません! 信じるかどうかは自由ですが、この章で議論した話題にはもっともっと学ぶべきことがあります: 第17章ではトレイトオブジェクトを議論します。これはトレイトを使用する別の手段です。 第19章では、ライフタイム注釈が関わるもっと複雑な筋書きと何か高度な型システムの機能を講義します。 ですが次は、コードがあるべき通りに動いていることを確かめられるように、Rustでテストを書く方法を学びます。","breadcrumbs":"ジェネリック型、トレイト、ライフタイム » まとめ","id":"191","title":"まとめ"},"192":{"body":"1972年のエッセイ「謙虚なプログラマ」でエドガー・W・ダイクストラは以下のように述べています。 「プログラムのテストは、バグの存在を示すには非常に効率的な手法であるが、 バグの不在を示すには望み薄く不適切である」と。これは、できるだけテストを試みるべきではないということではありません。 プログラムの正当性は、どこまで自分のコードが意図していることをしているかなのです。 Rustは、プログラムの正当性に重きを置いて設計されていますが、 正当性は複雑で、単純に証明することはありません。Rustの型システムは、 この重荷の多くの部分を肩代わりしてくれますが、型システムはあらゆる種類の不当性を捕捉してはくれません。 ゆえに、Rustでは、言語内で自動化されたソフトウェアテストを書くことをサポートしているのです。 例として、渡された何かの数値に2を足すadd_twoという関数を書くとしましょう。 この関数のシグニチャは、引数に整数を取り、結果として整数を返します。 この関数を実装してコンパイルすると、コンパイラはこれまでに学んできた型チェックと借用チェックを全て行い、 例えば、Stringの値や無効な参照をこの関数に渡していないかなどを確かめるのです。 ところが、コンパイラはプログラマがまさしく意図したことを関数が実行しているかどうかは確かめ られません 。 つまり、そうですね、引数に10を足したり、50を引いたりするのではなく、引数に2を足していることです。 そんな時に、テストは必要になるのです。 例えば、add_two関数に3を渡した時に、戻り値は5であることをアサーションするようなテストを書くことができます。 コードに変更を加えた際にこれらのテストを走らせ、既存の正当な振る舞いが変わっていないことを確認できます。 テストは、複雑なスキルです: いいテストの書き方をあらゆる方面から講義することは1章だけではできないのですが、 Rustのテスト機構のメカニズムについて議論します。テストを書く際に利用可能になるアノテーションとマクロについて、 テストを実行するのに提供されているオプションと標準の動作、さらにテストをユニットテストや統合テストに体系化する方法について語ります。","breadcrumbs":"自動テストを書く","id":"192","title":"自動テストを書く"},"193":{"body":"テストは、テスト以外のコードが想定された方法で機能していることを実証するRustの関数です。 テスト関数の本体は、典型的には以下の3つの動作を行います: 必要なデータや状態をセットアップする。 テスト対象のコードを走らせる。 結果が想定通りであることを断定(以下、アサーションという)する。 Rustが、特にこれらの動作を行うテストを書くために用意している機能を見ていきましょう。 これには、test属性、いくつかのマクロ、should_panic属性が含まれます。","breadcrumbs":"自動テストを書く » テストの記述法","id":"193","title":"テストの記述法"},"194":{"body":"最も単純には、Rustにおけるテストはtest属性で注釈された関数のことです。属性とは、 Rustコードの欠片に関するメタデータです; 一例を挙げれば、構造体とともに第5章で使用したderive属性です。 関数をテスト関数に変えるには、fnの前に#[test]を付け加えてください。 cargo testコマンドでテストを実行したら、コンパイラはtest属性で注釈された関数を走らせるテスト用バイナリをビルドし、 各テスト関数が通過したか失敗したかを報告します。 新しいライブラリプロジェクトをCargoで作ると、テスト関数付きのテストモジュールが自動的に生成されます。 このモジュールのおかげで、新しいプロジェクトを始めるたびにテスト関数の正しい構造とか文法をいちいち検索しなくてすみます。 ここに好きな数だけテスト関数やテストモジュールを追加すればいいというわけです! まず、実際にはコードをテストしない、自動生成されたテンプレートのテストで実験して、テストの動作の性質をいくらか学びましょう。 その後で、以前書いたコードを呼び出し、振る舞いが正しいことをアサーションする、ホンモノのテストを書きましょう。 adderという新しいライブラリプロジェクトを生成しましょう: $ cargo new adder --lib Created library `adder` project\n$ cd adder adderライブラリの src/lib.rs ファイルの中身は、リスト11-1のような見た目のはずです。 ファイル名: src/lib.rs #[cfg(test)]\nmod tests { #[test] fn it_works() { assert_eq!(2 + 2, 4); }\n}\n# # fn main() {} リスト11-1: cargo newで自動生成されたテストモジュールと関数 とりあえず、最初の2行は無視し、関数に集中してその動作法を見ましょう。 fn行の#[test]注釈に注目してください: この属性は、これがテスト関数であることを示すので、 テスト実行機はこの関数をテストとして扱うとわかるのです。さらに、testsモジュール内にはテスト関数以外の関数を入れ、 一般的なシナリオをセットアップしたり、共通の処理を行う手助けをしたりもできるので、 #[test]属性でどの関数がテストかを示す必要があるのです。 関数本体は、assert_eq!マクロを使用して、2 + 2が4に等しいことをアサーションしています。 このアサーションは、典型的なテストのフォーマット例をなしているわけです。走らせてこのテストが通る(訳注:テストが成功する、の意味。英語でpassということから、このように表現される)ことを確かめましょう。 cargo testコマンドでプロジェクトにあるテストが全て実行されます。リスト11-2に示したようにですね。 $ cargo test Compiling adder v0.1.0 (file:///projects/adder) Finished test [unoptimized + debuginfo] target(s) in 0.57s Running target/debug/deps/adder-92948b65e88960b4 running 1 test\ntest tests::it_works ... 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 リスト11-2: 自動生成されたテストを走らせた出力 Cargoがテストをコンパイルし、走らせました。Compiling, Finished, Runningの行の後にrunning 1 testの行があります。 次行が、生成されたテスト関数のit_worksという名前とこのテストの実行結果、okを示しています。 テスト実行の総合的なまとめが次に出現します。test result:ok.というテキストは、 全テストが通ったことを意味し、1 passed; 0 failedと読める部分は、通過または失敗したテストの数を合計しているのです。 無視すると指定したテストは何もなかったため、まとめは0 ignoredと示しています。 また、実行するテストにフィルタをかけもしなかったので、まとめの最後に0 filtered outと表示されています。 テストを無視することとフィルタすることに関しては次の節、 テストの実行され方を制御する で語ります。 0 measuredという統計は、パフォーマンスを測定するベンチマークテスト用です。 ベンチマークテストは、本書記述の時点では、nightly版のRustでのみ利用可能です。 詳しくは、 ベンチマークテストのドキュメンテーション を参照されたし。 テスト出力の次の部分、つまりDoc-tests adderで始まる部分は、ドキュメンテーションテストの結果用のものです。 まだドキュメンテーションテストは何もないものの、コンパイラは、APIドキュメントに現れるどんなコード例もコンパイルできます。 この機能により、ドキュメントとコードを同期することができるわけです。ドキュメンテーションテストの書き方については、 第14章の テストとしてのドキュメンテーションコメント 節で議論しましょう。今は、Doc-tests出力は無視します。 テストの名前を変更してどうテスト出力が変わるか確かめましょう。it_works関数を違う名前、explorationなどに変えてください。 そう、以下のように: ファイル名: src/lib.rs #[cfg(test)]\nmod tests { #[test] fn exploration() { assert_eq!(2 + 2, 4); }\n}\n# # fn main() {} そして、cargo testを再度走らせます。これで出力がit_worksの代わりにexplorationと表示しています: $ cargo test Compiling adder v0.1.0 (file:///projects/adder) Finished test [unoptimized + debuginfo] target(s) in 0.59s Running target/debug/deps/adder-92948b65e88960b4 running 1 test\ntest tests::exploration ... 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 別のテストを追加しますが、今回は失敗するテストにしましょう!テスト関数内の何かがパニックすると、 テストは失敗します。各テストは、新規スレッドで実行され、メインスレッドが、テストスレッドが死んだと確認した時、 テストは失敗と印づけられます。第9章でパニックを引き起こす最も単純な方法について語りました。 そう、panic!マクロを呼び出すことですね。 src/lib.rs ファイルがリスト11-3のような見た目になるよう、 新しいテストanotherを入力してください。 ファイル名: src/lib.rs #[cfg(test)]\nmod tests { #[test] fn exploration() { assert_eq!(2 + 2, 4); } #[test] fn another() { //このテストを失敗させる panic!(\"Make this test fail\"); }\n}\n# # fn main() {} リスト11-3: panic!マクロを呼び出したために失敗する2番目のテストを追加する cargo testで再度テストを走らせてください。出力はリスト11-4のようになるはずであり、 explorationテストは通り、anotherは失敗したと表示されます。 $ cargo test Compiling adder v0.1.0 (file:///projects/adder) Finished test [unoptimized + debuginfo] target(s) in 0.72s Running target/debug/deps/adder-92948b65e88960b4 running 2 tests\ntest tests::another ... FAILED\ntest tests::exploration ... ok failures: ---- tests::another stdout ----\nthread 'main' panicked at 'Make this test fail', src/lib.rs:10:9\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace. failures: tests::another test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out error: test failed, to rerun pass '--lib' リスト11-4: 一つのテストが通り、一つが失敗するときのテスト結果 okの代わりにtest test::anotherの行は、FAILEDを表示しています。個々の結果とまとめの間に、 2つ新たな区域ができました: 最初の区域は、失敗したテスト各々の具体的な理由を表示しています。 今回の場合、anotherは'Make this test fail'でパニックしたために失敗し、 これは、 src/lib.rs ファイルの10行で起きました。次の区域は失敗したテストの名前だけを列挙しています。 これは、テストがたくさんあり、失敗したテストの詳細がたくさん表示されるときに有用になります。 失敗したテストの名前を使用してそのテストだけを実行し、より簡単にデバッグすることができます。 テストの実行方法については、 テストの実行され方を制御する 節でもっと語りましょう。 サマリー行が最後に出力されています: 総合的に言うと、テスト結果はFAILEDでした。 一つのテストが通り、一つが失敗したわけです。 様々な状況でのテスト結果がどんな風になるか見てきたので、テストを行う際に有用になるpanic!以外のマクロに目を向けましょう。","breadcrumbs":"自動テストを書く » テスト関数の構成","id":"194","title":"テスト関数の構成"},"195":{"body":"assert!マクロは、標準ライブラリで提供されていますが、テスト内の何らかの条件がtrueと評価されることを確かめたいときに有効です。 assert!マクロには、論理値に評価される引数を与えます。その値がtrueなら、 assert!は何もせず、テストは通ります。その値がfalseなら、assert!マクロはpanic!マクロを呼び出し、 テストは失敗します。assert!マクロを使用することで、コードが意図した通りに機能していることを確認する助けになるわけです。 第5章のリスト5-15で、Rectangle構造体とcan_holdメソッドを使用しました。リスト11-5でもそれを繰り返しています。 このコードを src/lib.rs ファイルに放り込み、assert!マクロでそれ用のテストを何か書いてみましょう。 ファイル名: src/lib.rs #[derive(Debug)]\nstruct Rectangle { width: u32, height: u32,\n} impl Rectangle { fn can_hold(&self, other: &Rectangle) -> bool { self.width > other.width && self.height > other.height }\n}\n# # fn main() {} リスト11-5: 第5章からRectangle構造体とそのcan_holdメソッドを使用する can_holdメソッドは論理値を返すので、assert!マクロの完璧なユースケースになるわけです。 リスト11-6で、幅が8、高さが7のRectangleインスタンスを生成し、これが幅5、 高さ1の別のRectangleインスタンスを保持できるとアサーションすることでcan_holdを用いるテストを書きます。 ファイル名: src/lib.rs # #[derive(Debug)]\n# struct Rectangle {\n# width: u32,\n# height: u32,\n# }\n# # impl Rectangle {\n# fn can_hold(&self, other: &Rectangle) -> bool {\n# self.width > other.width && self.height > other.height\n# }\n# }\n# #[cfg(test)]\nmod tests { use super::*; #[test] fn larger_can_hold_smaller() { let larger = Rectangle { width: 8, height: 7, }; let smaller = Rectangle { width: 5, height: 1, }; assert!(larger.can_hold(&smaller)); }\n}\n# # fn main() {} リスト11-6: より大きな長方形がより小さな長方形を確かに保持できるかを確認するcan_hold用のテスト testsモジュール内に新しい行を加えたことに注目してください: use super::*です。 testsモジュールは、第7章の モジュールツリーの要素を示すためのパス 節で講義した通常の公開ルールに従う普通のモジュールです。 testsモジュールは、内部モジュールなので、外部モジュール内のテスト配下にあるコードを内部モジュールのスコープに持っていく必要があります。 ここではglobを使用して、外部モジュールで定義したもの全てがこのtestsモジュールでも使用可能になるようにしています。 テストはlarger_can_hold_smallerと名付け、必要なRectangleインスタンスを2つ生成しています。 そして、assert!マクロを呼び出し、larger.can_hold(&smaller)の呼び出し結果を渡しました。 この式は、trueを返すと考えられるので、テストは通るはずです。確かめましょう! $ cargo test Compiling rectangle v0.1.0 (file:///projects/rectangle) Finished test [unoptimized + debuginfo] target(s) in 0.66s Running target/debug/deps/rectangle-6584c4561e48942e running 1 test\ntest tests::larger_can_hold_smaller ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out Doc-tests rectangle running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out 通ります!別のテストを追加しましょう。今回は、小さい長方形は、より大きな長方形を保持できないことをアサーションします。 ファイル名: src/lib.rs # #[derive(Debug)]\n# struct Rectangle {\n# width: u32,\n# height: u32,\n# }\n# # impl Rectangle {\n# fn can_hold(&self, other: &Rectangle) -> bool {\n# self.width > other.width && self.height > other.height\n# }\n# }\n# #[cfg(test)]\nmod tests { use super::*; #[test] fn larger_can_hold_smaller() { // --snip--\n# let larger = Rectangle {\n# width: 8,\n# height: 7,\n# };\n# let smaller = Rectangle {\n# width: 5,\n# height: 1,\n# };\n# # assert!(larger.can_hold(&smaller)); } #[test] fn smaller_cannot_hold_larger() { let larger = Rectangle { width: 8, height: 7, }; let smaller = Rectangle { width: 5, height: 1, }; assert!(!smaller.can_hold(&larger)); }\n}\n# # fn main() {} 今回の場合、can_hold関数の正しい結果はfalseなので、その結果をassert!マクロに渡す前に反転させる必要があります。 結果として、can_holdがfalseを返せば、テストは通ります。 $ cargo test Compiling rectangle v0.1.0 (file:///projects/rectangle) Finished test [unoptimized + debuginfo] target(s) in 0.66s Running target/debug/deps/rectangle-6584c4561e48942e running 2 tests\ntest tests::larger_can_hold_smaller ... ok\ntest tests::smaller_cannot_hold_larger ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out Doc-tests rectangle running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out 通るテストが2つ!さて、コードにバグを導入したらテスト結果がどうなるか確認してみましょう。 幅を比較する大なり記号を小なり記号で置き換えてcan_holdメソッドの実装を変更しましょう: # #[derive(Debug)]\n# struct Rectangle {\n# width: u32,\n# height: u32,\n# }\n# // --snip--\nimpl Rectangle { fn can_hold(&self, other: &Rectangle) -> bool { self.width < other.width && self.height > other.height }\n}\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# # #[test]\n# fn larger_can_hold_smaller() {\n# let larger = Rectangle {\n# width: 8,\n# height: 7,\n# };\n# let smaller = Rectangle {\n# width: 5,\n# height: 1,\n# };\n# # assert!(larger.can_hold(&smaller));\n# }\n# # #[test]\n# fn smaller_cannot_hold_larger() {\n# let larger = Rectangle {\n# width: 8,\n# height: 7,\n# };\n# let smaller = Rectangle {\n# width: 5,\n# height: 1,\n# };\n# # assert!(!smaller.can_hold(&larger));\n# }\n# }\n# # fn main() {} テストを実行すると、以下のような出力をします: $ cargo test Compiling rectangle v0.1.0 (file:///projects/rectangle) Finished test [unoptimized + debuginfo] target(s) in 0.66s Running target/debug/deps/rectangle-6584c4561e48942e running 2 tests\ntest tests::larger_can_hold_smaller ... FAILED\ntest tests::smaller_cannot_hold_larger ... ok failures: ---- tests::larger_can_hold_smaller stdout ----\nthread 'main' panicked at 'assertion failed: larger.can_hold(&smaller)', src/lib.rs:28:9\n(スレッド'main'はsrc/lib.rs:28:9の'assertion failed: larger.can_hold(&smaller)'でパニックしました)\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace. failures: tests::larger_can_hold_smaller test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out error: test failed, to rerun pass '--lib' テストによりバグが捕捉されました!larger.widthが8、smaller.widthが5なので、 can_hold内の幅の比較が今はfalseを返すようになったのです: 8は5より小さくないですからね。","breadcrumbs":"自動テストを書く » assert!マクロで結果を確認する","id":"195","title":"assert!マクロで結果を確認する"},"196":{"body":"機能をテストする一般的な方法は、テスト下にあるコードの結果をコードが返すと期待される値と比較して、 等しいと確かめることです。これをassertマクロを使用して==演算子を使用した式を渡すことで行うこともできます。 しかしながら、これはありふれたテストなので、標準ライブラリには1組のマクロ(assert_eq!とassert_ne!)が提供され、 このテストをより便利に行うことができます。これらのマクロはそれぞれ、二つの引数を比べ、等しいかと等しくないかを確かめます。 また、アサーションが失敗したら二つの値の出力もし、テストが失敗した 原因 を確認しやすくなります。 一方でassert!マクロは、==式の値がfalseになったことしか示さず、falseになった原因の値は出力しません。 リスト11-7において、引数に2を加えて結果を返すadd_twoという名前の関数を書いています。 そして、assert_eq!マクロでこの関数をテストしています。 ファイル名: src/lib.rs pub fn add_two(a: i32) -> i32 { a + 2\n} #[cfg(test)]\nmod tests { use super::*; #[test] fn it_adds_two() { assert_eq!(4, add_two(2)); }\n}\n# # fn main() {} リスト11-7: assert_eq!マクロでadd_two関数をテストする $ cargo test Compiling adder v0.1.0 (file:///projects/adder) Finished test [unoptimized + debuginfo] target(s) in 0.58s Running target/debug/deps/adder-92948b65e88960b4 running 1 test\ntest tests::it_adds_two ... 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 assert_eq!マクロに与えた第1引数の4は、add_two(2)の呼び出し結果と等しいです。 このテストの行はtest tests::it_adds_two ... okであり、okというテキストはテストが通ったことを示しています! コードにバグを仕込んで、assert_eq!を使ったテストが失敗した時にどんな見た目になるのか確認してみましょう。 add_two関数の実装を代わりに3を足すように変えてください: pub fn add_two(a: i32) -> i32 { a + 3\n}\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# # #[test]\n# fn it_adds_two() {\n# assert_eq!(4, add_two(2));\n# }\n# }\n# # fn main() {} テストを再度実行します: $ cargo test Compiling adder v0.1.0 (file:///projects/adder) Finished test [unoptimized + debuginfo] target(s) in 0.61s Running target/debug/deps/adder-92948b65e88960b4 running 1 test\ntest tests::it_adds_two ... FAILED failures: ---- tests::it_adds_two stdout ----\nthread 'main' panicked at 'assertion failed: `(left == right)` left: `4`, right: `5`', src/lib.rs:11:9\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace. failures: tests::it_adds_two test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out error: test failed, to rerun pass '--lib' テストがバグを捕捉しました!it_adds_twoのテストは失敗し、assertion failed: `(left == right)`というメッセージを表示し、 leftは4で、rightは5だったと示しています。このメッセージは有用で、デバッグを開始する助けになります: assert_eq!のleft引数は4だったが、add_two(2)があるright引数は5だったことを意味しています。 二つの値が等しいとアサーションを行う関数の引数を expectedとactualと呼び、引数を指定する順序が問題になる言語やテストフレームワークもあることに注意してください。 ですがRustでは、leftとrightと呼ばれ、期待する値とテスト下のコードが生成する値を指定する順序は 問題になりません。今回のテストのアサーションをassert_eq!(add_two(2), 4)と書くこともでき、 そうすると失敗メッセージは、assertion failed: `(left == right)`となり、 leftが5でrightが4と表示されるでしょう。 assert_ne!マクロは、与えた2つの値が等しくなければ通り、等しければ失敗します。 このマクロは、値が何になる だろう か確信が持てないけれども、コードが意図した通りに動いていれば、 確実にこの値にはなら ないだろう とわかっているような場合に最も有用になります。例えば、 入力を何らかの手段で変え(て出力す)ることが保証されているけれども、入力の変え方がテストを実行する曜日に依存する関数をテストしているなら、 アサーションすべき最善の事柄は、関数の出力が入力と等しくないことかもしれません。 内部的には、assert_eq!とassert_ne!マクロは、それぞれ==と!=演算子を使用しています。 アサーションが失敗すると、これらのマクロは引数をデバッグフォーマットを使用してプリントするので、 比較対象の値はPartialEqとDebugトレイトを実装していなければなりません。 すべての組み込み型と、ほぼすべての標準ライブラリの型はこれらのトレイトを実装しています。 自分で定義した構造体やenumについては、 その型の値が等しいか等しくないかをアサーションするために、PartialEqを実装する必要があるでしょう。 それが失敗した時にその値をプリントできるように、Debugを実装する必要もあるでしょう。 第5章のリスト5-12で触れたように、どちらのトレイトも導出可能なトレイトなので、 これは通常、単純に構造体やenum定義に#[derive(PartialEq, Debug)]という注釈を追加するだけですみます。 これらやその他の導出可能なトレイトに関する詳細については、付録C、 導出可能なトレイト をご覧ください。","breadcrumbs":"自動テストを書く » assert_eq!とassert_ne!マクロで等値性をテストする","id":"196","title":"assert_eq!とassert_ne!マクロで等値性をテストする"},"197":{"body":"さらに、assert!、assert_eq!、assert_ne!の追加引数として、失敗メッセージと共にカスタムのメッセージが表示されるよう、 追加することもできます。assert!の1つの必須引数の後に、 あるいはassert_eq!とassert_ne!の2つの必須引数の後に指定された引数はすべてformat!マクロに渡されるので、 (format!マクロについては第8章の +演算子、またはformat!マクロで連結 節で議論しました)、 {}プレースホルダーを含むフォーマット文字列とこのプレースホルダーに置き換えられる値を渡すことができます。 カスタムメッセージは、アサーションがどんな意味を持つかドキュメント化するのに役に立ちます; もしテストが失敗した時、コードにどんな問題があるのかをよりしっかり把握できるはずです。 例として、人々に名前で挨拶をする関数があり、関数に渡した名前が出力に出現することをテストしたいとしましょう: ファイル名: src/lib.rs pub fn greeting(name: &str) -> String { format!(\"Hello {}!\", name)\n} #[cfg(test)]\nmod tests { use super::*; #[test] fn greeting_contains_name() { let result = greeting(\"Carol\"); assert!(result.contains(\"Carol\")); }\n}\n# # fn main() {} このプログラムの要件はまだ取り決められておらず、挨拶の先頭のHelloというテキストはおそらく変わります。 要件が変わった時にテストを更新しなくてもよいようにしたいと考え、 greeting関数から返る値と正確な等値性を確認するのではなく、出力が入力引数のテキストを含むことをアサーションするだけにします。 greetingがnameを含まないように変更してこのコードにバグを仕込み、このテストの失敗がどんな風になるのか確かめましょう: pub fn greeting(name: &str) -> String { String::from(\"Hello!\")\n}\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# # #[test]\n# fn greeting_contains_name() {\n# let result = greeting(\"Carol\");\n# assert!(result.contains(\"Carol\"));\n# }\n# }\n# # fn main() {} このテストを実行すると、以下のように出力されます: $ cargo test Compiling greeter v0.1.0 (file:///projects/greeter) Finished test [unoptimized + debuginfo] target(s) in 0.91s Running target/debug/deps/greeter-170b942eb5bf5e3a running 1 test\ntest tests::greeting_contains_name ... FAILED failures: ---- tests::greeting_contains_name stdout ----\nthread 'main' panicked at 'assertion failed: result.contains(\"Carol\")', src/lib.rs:12:9\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace. failures: tests::greeting_contains_name test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out error: test failed, to rerun pass '--lib' この結果は、アサーションが失敗し、どの行にアサーションがあるかを示しているだけです。 今回の場合、失敗メッセージがgreeting関数から得た値を出力していればより有用でしょう。 テスト関数を変更し、 greeting関数から得た実際の値で埋められるプレースホルダーを含むフォーマット文字列からなるカスタムの失敗メッセージを与えてみましょう。 # pub fn greeting(name: &str) -> String {\n# String::from(\"Hello!\")\n# }\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# #[test] fn greeting_contains_name() { let result = greeting(\"Carol\"); assert!( result.contains(\"Carol\"), //挨拶(greeting)は名前を含んでいません。その値は`{}`でした \"Greeting did not contain name, value was `{}`\", result ); }\n# } これでテストを実行したら、より有益なエラーメッセージが得られるでしょう: $ cargo test Compiling greeter v0.1.0 (file:///projects/greeter) Finished test [unoptimized + debuginfo] target(s) in 0.93s Running target/debug/deps/greeter-170b942eb5bf5e3a running 1 test\ntest tests::greeting_contains_name ... FAILED failures: ---- tests::greeting_contains_name stdout ----\nthread 'main' panicked at 'Greeting did not contain name, value was `Hello!`', src/lib.rs:12:9\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace. failures: tests::greeting_contains_name test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out error: test failed, to rerun pass '--lib' 実際に得られた値がテスト出力に表示されているので、起こると想定していたものではなく、 起こったものをデバッグするのに役に立ちます。","breadcrumbs":"自動テストを書く » カスタムの失敗メッセージを追加する","id":"197","title":"カスタムの失敗メッセージを追加する"},"198":{"body":"期待する正しい値をコードが返すことを確認することに加えて、想定通りにコードがエラー状態を扱っていることを確認するのも重要です。 例えば、第9章のリスト9-10で生成したGuess型を考えてください。Guessを使用する他のコードは、 Guessのインスタンスは1から100の範囲の値しか含まないという保証に依存しています。 その範囲外の値でGuessインスタンスを生成しようとするとパニックすることを確認するテストを書くことができます。 これは、テスト関数にshould_panicという別の属性を追加することで達成できます。 この属性は、関数内のコードがパニックしたら、テストを通過させます。つまり、 関数内のコードがパニックしなかったら、テストは失敗するわけです。 リスト11-8は、予想どおりにGuess::newのエラー条件が発生していることを確認するテストを示しています。 ファイル名: src/lib.rs pub struct Guess { value: i32,\n} impl Guess { pub fn new(value: i32) -> Guess { if value < 1 || value > 100 { //予想値は1から100の間でなければなりませんが、{}でした。 panic!(\"Guess value must be between 1 and 100, got {}.\", value); } Guess { value } }\n} #[cfg(test)]\nmod tests { use super::*; #[test] #[should_panic] fn greater_than_100() { Guess::new(200); }\n}\n# # fn main() {} リスト11-8: 状況がpanic!を引き起こすとテストする #[test]属性の後、適用するテスト関数の前に#[should_panic]属性を配置しています。 このテストが通るときの結果を見ましょう: $ cargo test Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished test [unoptimized + debuginfo] target(s) in 0.58s Running target/debug/deps/guessing_game-57d70c3acb738f4d running 1 test\ntest tests::greater_than_100 ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out Doc-tests guessing_game running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out よさそうですね!では、値が100より大きいときにnew関数がパニックするという条件を除去することでコードにバグを導入しましょう: # pub struct Guess {\n# value: i32,\n# }\n# // --snip--\nimpl Guess { pub fn new(value: i32) -> Guess { if value < 1 { //予想値は1から100の間でなければなりませんが、{}でした。 panic!(\"Guess value must be between 1 and 100, got {}.\", value); } Guess { value } }\n}\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# # #[test]\n# #[should_panic]\n# fn greater_than_100() {\n# Guess::new(200);\n# }\n# }\n# # fn main() {} リスト11-8のテストを実行すると、失敗するでしょう: $ cargo test Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished test [unoptimized + debuginfo] target(s) in 0.62s Running target/debug/deps/guessing_game-57d70c3acb738f4d running 1 test\ntest tests::greater_than_100 ... FAILED failures: ---- tests::greater_than_100 stdout ----\nnote: test did not panic as expected failures: tests::greater_than_100 test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out error: test failed, to rerun pass '--lib' この場合、それほど役に立つメッセージは得られませんが、テスト関数に目を向ければ、 #[should_panic]で注釈されていることがわかります。得られた失敗は、 テスト関数のコードがパニックを引き起こさなかったことを意味するのです。 should_panicを使用するテストは不正確なこともあります。なぜなら、コードが何らかのパニックを起こしたことしか示さないからです。 should_panicのテストは、起きると想定していたもの以外の理由でテストがパニックしても通ってしまうのです。 should_panicのテストの正確を期すために、should_panic属性にexpected引数を追加することもできます。 このテストハーネスは、失敗メッセージに与えられたテキストが含まれていることを確かめてくれます。 例えば、リスト11-9の修正されたGuessのコードを考えてください。ここでは、 new関数は、値が大きすぎるか小さすぎるかによって異なるメッセージでパニックします。 ファイル名: src/lib.rs # pub struct Guess {\n# value: i32,\n# }\n# // --snip--\nimpl Guess { pub fn new(value: i32) -> Guess { if value < 1 { panic!( //予想値は1以上でなければなりませんが、{}でした。 \"Guess value must be greater than or equal to 1, got {}.\", value ); } else if value > 100 { panic!( //予想値は100以下でなければなりませんが、{}でした。 \"Guess value must be less than or equal to 100, got {}.\", value ); } Guess { value } }\n} #[cfg(test)]\nmod tests { use super::*; #[test] //予想値は100以下でなければなりません #[should_panic(expected = \"Guess value must be less than or equal to 100\")] fn greater_than_100() { Guess::new(200); }\n}\n# # fn main() {} リスト11-9: 状況が特定のパニックメッセージでpanic!を引き起こすことをテストする should_panic属性のexpected引数に置いた値がGuess::new関数がパニックしたメッセージの一部になっているので、 このテストは通ります。予想されるパニックメッセージ全体を指定することもでき、今回の場合、 Guess value must be less than or equal to 100, got 200.となります。 should_panicの予想される引数に何を指定するかは、パニックメッセージのどこが固有でどこが動的か、 またテストをどの程度正確に行いたいかによります。今回の場合、パニックメッセージの一部でも、テスト関数内のコードが、 else if value > 100の場合を実行していると確認するのに事足りるのです。 expectedメッセージありのshould_panicテストが失敗すると何が起きるのが確かめるために、 if value < 1とelse if value > 100ブロックの本体を入れ替えることで再度コードにバグを仕込みましょう: # pub struct Guess {\n# value: i32,\n# }\n# # impl Guess {\n# pub fn new(value: i32) -> Guess { if value < 1 { panic!( //予想値は100以下でなければなりませんが、{}でした。 \"Guess value must be less than or equal to 100, got {}.\", value ); } else if value > 100 { panic!( //予想値は1以上でなければなりませんが、{}でした。 \"Guess value must be greater than or equal to 1, got {}.\", value ); }\n# # Guess { value }\n# }\n# }\n# # #[cfg(test)]\n# mod tests {\n# use super::*;\n# # #[test]\n# #[should_panic(expected = \"Guess value must be less than or equal to 100\")]\n# fn greater_than_100() {\n# Guess::new(200);\n# }\n# } should_panicテストを実行すると、今回は失敗するでしょう: $ cargo test Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished test [unoptimized + debuginfo] target(s) in 0.66s Running target/debug/deps/guessing_game-57d70c3acb738f4d running 1 test\ntest tests::greater_than_100 ... FAILED failures: ---- tests::greater_than_100 stdout ----\nthread 'main' panicked at 'Guess value must be greater than or equal to 1, got 200.', src/lib.rs:13:13\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.\nnote: panic did not contain expected string panic message: `\"Guess value must be greater than or equal to 1, got 200.\"`, expected substring: `\"Guess value must be less than or equal to 100\"` failures: tests::greater_than_100 test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out error: test failed, to rerun pass '--lib' この失敗メッセージは、このテストが確かに予想通りパニックしたことを示していますが、 パニックメッセージは、予想される文字列の'Guess value must be less than or equal to 100'を含んでいませんでした。 実際に得られたパニックメッセージは今回の場合、Guess value must be greater than or equal to 1, got 200でした。 そうしてバグの所在地を割り出し始めることができるわけです!","breadcrumbs":"自動テストを書く » should_panicでパニックを確認する","id":"198","title":"should_panicでパニックを確認する"},"199":{"body":"これまでは、失敗するとパニックするようなテストを書いてきましたが、 Resultを使うようなテストを書くこともできます! 以下は、Listing 11-1のテストを、Resultを使い、パニックする代わりにErrを返すように書き直したものです: #[cfg(test)]\nmod tests { #[test] fn it_works() -> Result<(), String> { if 2 + 2 == 4 { Ok(()) } else { Err(String::from(\"two plus two does not equal four\")) } }\n} it_works関数の戻り値の型はResult<(), String>になりました。 関数内でassert_eq!マクロを呼び出す代わりに、テストが成功すればOk(())を、失敗すればErrにStringを入れて返すようにします。 Result を返すようなテストを書くと、?演算子をテストの中で使えるようになります。 これは、テスト内で何らかの工程がErrヴァリアントを返したときに失敗するべきテストを書くのに便利です。 Resultを使うテストに#[should_panic]注釈を使うことはできません。 テストが失敗しなければならないときは、直接Err値を返してください。 今やテスト記法を複数知ったので、テストを走らせる際に起きていることに目を向け、 cargo testで使用できるいろんなオプションを探究しましょう。","breadcrumbs":"自動テストを書く » Resultをテストで使う","id":"199","title":"Resultをテストで使う"},"2":{"body":"注釈: この本のこの版は、本として利用可能な The Rust Programming Language と、 No Starch Press のebook形式と同じです。 The Rust Programming Language へようこそ。Rustに関する入門書です。 Rustプログラミング言語は、高速で信頼できるソフトウェアを書く手助けをしてくれます。 高レベルのエルゴノミクス(訳注: ergonomicsとは、人間工学的という意味。砕いて言えば、人間に優しいということ)と低レベルの制御は、 しばしばプログラミング言語の設計においてトレードオフの関係になります; Rustは、その衝突に挑戦しています。バランスのとれた強力な技術の許容量と素晴らしい開発者経験を通して、 Rustは伝統的にそれらの制御と紐付いていた困難全てなしに低レベルの詳細(メモリ使用など)を制御する選択肢を与えてくれます。","breadcrumbs":"導入","id":"2","title":"導入"},"20":{"body":"Rustをインストールしたので、最初のRustプログラムを書きましょう。新しい言語を学ぶ際に、 Hello, world!というテキストを画面に出力する小さなプログラムを書くことは伝統的なことなので、 ここでも同じようにしましょう! 注釈: この本は、コマンドラインに基礎的な馴染みがあることを前提にしています。Rustは、編集やツール、 どこにコードがあるかについて特定の要求をしないので、コマンドラインではなくIDEを使用することを好むのなら、 どうぞご自由にお気に入りのIDEを使用してください。今では、多くのIDEがなんらかの形でRustをサポートしています; 詳しくは、IDEのドキュメンテーションをご覧ください。最近、Rustチームは優れたIDEサポートを有効にすることに注力し、 その前線で急激に成果があがっています!","breadcrumbs":"事始め » Hello, World!","id":"20","title":"Hello, World!"},"200":{"body":"cargo runがコードをコンパイルし、出来上がったバイナリを走らせるのと全く同様に、 cargo testはコードをテストモードでコンパイルし、出来上がったテストバイナリを実行します。 コマンドラインオプションを指定してcargo testの既定動作を変更することができます。 例えば、cargo testで生成されるバイナリの既定動作は、テストを全て並行に実行し、 テスト実行中に生成された出力をキャプチャして出力が表示されるのを防ぎ、 テスト結果に関係する出力を読みやすくすることです。 コマンドラインオプションの中にはcargo testにかかるものや、出来上がったテストバイナリにかかるものがあります。 この2種の引数を区別するために、cargo testにかかる引数を--という区分記号の後に列挙し、 それからテストバイナリにかかる引数を列挙します。cargo test --helpを走らせると、cargo testで使用できるオプションが表示され、 cargo test -- --helpを走らせると、--という区分記号の後に使えるオプションが表示されます。","breadcrumbs":"自動テストを書く » テストの実行のされ方を制御する","id":"200","title":"テストの実行のされ方を制御する"},"201":{"body":"複数のテストを実行するとき、標準では、スレッドを使用して並行に走ります。これはつまり、 テストが早く実行し終わり、コードが機能しているいかんにかかわらず、反応をより早く得られることを意味します。 テストは同時に実行されているので、テストが相互や共有された環境を含む他の共通の状態に依存してないことを確かめてください。 現在の作業対象ディレクトリや環境変数などですね。 例えば、各テストがディスクに test_output.txt というファイルを作成し、何らかのデータを書き込むコードを走らせるとしてください。 そして、各テストはそのファイルのデータを読み取り、ファイルが特定の値を含んでいるとアサーションし、 その値は各テストで異なります。テストが同時に走るので、あるテストが、 他のテストが書き込んだり読み込んだりする間隙にファイルを上書きするかもしれません。 それから2番目のテストが失敗します。コードが不正だからではなく、 並行に実行されている間にテストがお互いに邪魔をしてしまったせいです。 各テストが異なるファイルに書き込むことを確かめるのが一つの解決策です; 別の解決策では、 一度に一つのテストを実行します。 並行にテストを実行したくなかったり、使用されるスレッド数をよりきめ細かく制御したい場合、 --test-threadsフラグと使用したいスレッド数をテストバイナリに送ることができます。 以下の例に目を向けてください: $ cargo test -- --test-threads=1 テストスレッドの数を1にセットし、並行性を使用しないようにプログラムに指示しています。 1スレッドのみを使用してテストを実行すると、並行に実行するより時間がかかりますが、 状態を共有していても、お互いに邪魔をすることはありません。","breadcrumbs":"自動テストを書く » テストを並行または連続して実行する","id":"201","title":"テストを並行または連続して実行する"},"202":{"body":"標準では、テストが通ると、Rustのテストライブラリは標準出力に出力されたものを全てキャプチャします。例えば、 テストでprintln!を呼び出してテストが通ると、println!の出力は、端末に表示されません; テストが通ったことを示す行しか見られないでしょう。テストが失敗すれば、 残りの失敗メッセージと共に、標準出力に出力されたものが全て見えるでしょう。 例として、リスト11-10は引数の値を出力し、10を返す馬鹿げた関数と通過するテスト1つ、失敗するテスト1つです。 ファイル名: src/lib.rs fn prints_and_returns_10(a: i32) -> i32 { //{}という値を得た println!(\"I got the value {}\", a); 10\n} #[cfg(test)]\nmod tests { use super::*; #[test] fn this_test_will_pass() { let value = prints_and_returns_10(4); assert_eq!(10, value); } #[test] fn this_test_will_fail() { let value = prints_and_returns_10(8); assert_eq!(5, value); }\n} リスト11-10: println!を呼び出す関数用のテスト これらのテストをcargo testで実行すると、以下のような出力を目の当たりにするでしょう: running 2 tests\ntest tests::this_test_will_pass ... ok\ntest tests::this_test_will_fail ... FAILED failures: ---- tests::this_test_will_fail stdout ---- I got the value 8\nthread 'tests::this_test_will_fail' panicked at 'assertion failed: `(left == right)` left: `5`, right: `10`', src/lib.rs:19:8\nnote: Run with `RUST_BACKTRACE=1` for a backtrace. failures: tests::this_test_will_fail test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out この出力のどこにも I got the value 4 と表示されていないことに注意してください。 これは、テストに合格した場合に出力されるものです。 その出力はキャプチャされてしまいました。 失敗したテストのからの出力 I got the value 8 がテストサマリー出力のセクションに表示され、テストが失敗した原因も示されます。 通過するテストについても出力される値が見たかったら、出力キャプチャ機能を--nocaptureフラグで無効化することができます: $ cargo test -- --nocapture リスト11-10のテストを--nocaptureフラグと共に再度実行したら、以下のような出力を目の当たりにします: running 2 tests\nI got the value 4\nI got the value 8\ntest tests::this_test_will_pass ... ok\nthread 'tests::this_test_will_fail' panicked at 'assertion failed: `(left == right)` left: `5`, right: `10`', src/lib.rs:19:8\nnote: Run with `RUST_BACKTRACE=1` for a backtrace.\ntest tests::this_test_will_fail ... FAILED failures: failures: tests::this_test_will_fail test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out テスト用の出力とテスト結果の出力がまぜこぜになっていることに注意してください; その理由は、前節で語ったようにテストが並行に実行されているからです。 -test-threads=1オプションと--nocaptureフラグを使ってみて、 その時、出力がどうなるか確かめてください!","breadcrumbs":"自動テストを書く » 関数の出力を表示する","id":"202","title":"関数の出力を表示する"},"203":{"body":"時々、全テストを実行すると時間がかかってしまうことがあります。特定の部分のコードしか対象にしていない場合、 そのコードに関わるテストのみを走らせたいかもしれません。cargo testに走らせたいテストの名前を引数として渡すことで、 実行するテストを選ぶことができます。 テストの一部を走らせる方法を模擬するために、リスト11-11に示したように、 add_two関数用に3つテストを作成し、走らせるテストを選択します。 ファイル名: src/lib.rs pub fn add_two(a: i32) -> i32 { a + 2\n} #[cfg(test)]\nmod tests { use super::*; #[test] fn add_two_and_two() { assert_eq!(4, add_two(2)); } #[test] fn add_three_and_two() { assert_eq!(5, add_two(3)); } #[test] fn one_hundred() { assert_eq!(102, add_two(100)); }\n} リスト11-11: 異なる名前の3つのテスト 以前見かけたように、引数なしでテストを走らせたら、全テストが並行に走ります: running 3 tests\ntest tests::add_two_and_two ... ok\ntest tests::add_three_and_two ... ok\ntest tests::one_hundred ... ok test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out 単独のテストを走らせる あらゆるテスト関数の名前をcargo testに渡して、そのテストのみを実行することができます: $ cargo test one_hundred Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs Running target/debug/deps/adder-06a75b4a1f2515e9 running 1 test\ntest tests::one_hundred ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out one_hundredという名前のテストだけが走りました; 他の2つのテストはその名前に合致しなかったのです。 まとめ行の最後に2 filtered outと表示することでテスト出力は、このコマンドが走らせた以上のテストがあることを知らせてくれています。 この方法では、複数のテストの名前を指定することはできません; cargo testに与えられた最初の値のみが使われるのです。 ですが、複数のテストを走らせる方法もあります。 複数のテストを実行するようフィルターをかける テスト名の一部を指定でき、その値に合致するあらゆるテストが走ります。例えば、 我々のテストの2つがaddという名前を含むので、cargo test addを実行することで、その二つを走らせることができます: $ cargo test add Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs Running target/debug/deps/adder-06a75b4a1f2515e9 running 2 tests\ntest tests::add_two_and_two ... ok\ntest tests::add_three_and_two ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out このコマンドは名前にaddを含むテストを全て実行し、one_hundredという名前のテストを除外しました。 また、テストが出現するモジュールがテスト名の一部になっていて、 モジュール名でフィルターをかけることで、あるモジュール内のテスト全てを実行できることに注目してください。","breadcrumbs":"自動テストを書く » 名前でテストの一部を実行する","id":"203","title":"名前でテストの一部を実行する"},"204":{"body":"時として、いくつかの特定のテストが実行するのに非常に時間がかかることがあり、 cargo testの実行のほとんどで除外したくなるかもしれません。引数として確かに実行したいテストを全て列挙するのではなく、 ここに示したように代わりに時間のかかるテストをignore属性で除外すると注釈することができます。 ファイル名: src/lib.rs #[test]\nfn it_works() { assert_eq!(2 + 2, 4);\n} #[test]\n#[ignore]\nfn expensive_test() { // 実行に1時間かかるコード // code that takes an hour to run\n} #[test]の後の除外したいテストに#[ignore]行を追加しています。これで、 テストを実行したら、it_worksは実行されるものの、expensive_testは実行されません: $ cargo test Compiling adder v0.1.0 (file:///projects/adder) Finished dev [unoptimized + debuginfo] target(s) in 0.24 secs Running target/debug/deps/adder-ce99bcc2479f4607 running 2 tests\ntest expensive_test ... ignored\ntest it_works ... ok test result: ok. 1 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out expensive_test関数は、ignoredと列挙されています。無視されるテストのみを実行したかったら、 cargo test -- --ignoredを使うことができます: $ cargo test -- --ignored Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs Running target/debug/deps/adder-ce99bcc2479f4607 running 1 test\ntest expensive_test ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out どのテストを走らせるか制御することで、結果が早く出ることを確かめることができるのです。 ignoredテストの結果を確認することが道理に合い、結果を待つだけの時間ができたときに、 代わりにcargo test -- --ignoredを走らせることができます。","breadcrumbs":"自動テストを書く » 特に要望のない限りテストを無視する","id":"204","title":"特に要望のない限りテストを無視する"},"205":{"body":"章の初めで触れたように、テストは複雑な鍛錬であり、人によって専門用語や体系化が異なります。 Rustのコミュニティでは、テストを2つの大きなカテゴリで捉えています: 単体テスト と 結合テスト です。 単体テストは小規模でより集中していて、個別に1回に1モジュールをテストし、非公開のインターフェイスもテストすることがあります。 結合テストは、完全にライブラリ外になり、他の外部コード同様に自分のコードを使用し、公開インターフェイスのみ使用し、 1テストにつき複数のモジュールを用いることもあります。 どちらのテストを書くのも、ライブラリの一部が個別かつ共同でしてほしいことをしていることを確認するのに重要なのです。","breadcrumbs":"自動テストを書く » テストの体系化","id":"205","title":"テストの体系化"},"206":{"body":"単体テストの目的は、残りのコードから切り離して各単位のコードをテストし、 コードが想定通り、動いたり動いていなかったりする箇所を迅速に特定することです。 単体テストは、テスト対象となるコードと共に、 src ディレクトリの各ファイルに置きます。 慣習は、各ファイルにtestsという名前のモジュールを作り、テスト関数を含ませ、 そのモジュールをcfg(test)で注釈することです。 テストモジュールと#[cfg(test)] testsモジュールの#[cfg(test)]という注釈は、コンパイラにcargo buildを走らせた時ではなく、cargo testを走らせた時にだけ、 テストコードをコンパイルし走らせるよう指示します。これにより、ライブラリをビルドしたいだけの時にはコンパイルタイムを節約し、 テストが含まれないので、コンパイル後の成果物のサイズも節約します。結合テストは別のディレクトリに存在することになるので、 #[cfg(test)]注釈は必要ないとわかるでしょう。しかしながら、単体テストはコードと同じファイルに存在するので、 #[cfg(test)]を使用してコンパイル結果に含まれないよう指定するのです。 この章の最初の節で新しいadderプロジェクトを生成した時に、Cargoがこのコードも生成してくれたことを思い出してください: ファイル名: src/lib.rs #[cfg(test)]\nmod tests { #[test] fn it_works() { assert_eq!(2 + 2, 4); }\n} このコードが自動生成されたテストモジュールです。cfgという属性は、 configuration を表していて、 コンパイラに続く要素が、ある特定の設定オプションを与えられたら、含まれるように指示します。 今回の場合、設定オプションは、testであり、言語によって提供されているテストをコンパイルし、 走らせるためのものです。cfg属性を使用することで、cargo testで積極的にテストを実行した場合のみ、 Cargoがテストコードをコンパイルします。これには、このモジュールに含まれるかもしれないヘルパー関数全ても含まれ、 #[test]で注釈された関数だけにはなりません。 非公開関数をテストする テストコミュニティ内で非公開関数を直接テストするべきかについては議論があり、 他の言語では非公開関数をテストするのは困難だったり、不可能だったりします。 あなたがどちらのテストイデオロギーを支持しているかに関わらず、Rustの公開性規則により、 非公開関数をテストすることが確かに可能です。リスト11-12の非公開関数internal_adderを含むコードを考えてください。 ファイル名: src/lib.rs pub fn add_two(a: i32) -> i32 { internal_adder(a, 2)\n} fn internal_adder(a: i32, b: i32) -> i32 { a + b\n} #[cfg(test)]\nmod tests { use super::*; #[test] fn internal() { assert_eq!(4, internal_adder(2, 2)); }\n} リスト11-12: 非公開関数をテストする internal_adder関数はpubとマークされていないものの、テストも単なるRustのコードであり、 testsモジュールもただのモジュールでしかないので、テスト内でinternal_adderを普通にインポートし呼び出すことができます。 非公開関数はテストするべきではないとお考えなら、Rustにはそれを強制するものは何もありません。","breadcrumbs":"自動テストを書く » 単体テスト","id":"206","title":"単体テスト"},"207":{"body":"Rustにおいて、結合テストは完全にライブラリ外のものです。他のコードと全く同様にあなたのライブラリを使用するので、 ライブラリの公開APIの一部である関数しか呼び出すことはできません。その目的は、 ライブラリのいろんな部分が共同で正常に動作しているかをテストすることです。 単体では正常に動くコードも、結合した状態だと問題を孕む可能性もあるので、 結合したコードのテストの範囲も同様に重要になるのです。結合テストを作成するには、 まず tests ディレクトリが必要になります。 tests ディレクトリ プロジェクトディレクトリのトップ階層、 src の隣に tests ディレクトリを作成します。 Cargoは、このディレクトリに結合テストのファイルを探すことを把握しています。 そして、このディレクトリ内にいくらでもテストファイルを作成することができ、 Cargoはそれぞれのファイルを個別のクレートとしてコンパイルします。 結合テストを作成しましょう。リスト11-12のコードが src/lib.rs ファイルにあるまま、 tests ディレクトリを作成し、 tests/integration_test.rs という名前の新しいファイルを生成し、 リスト11-13のコードを入力してください。 ファイル名: tests/integration_test.rs extern crate adder; #[test]\nfn it_adds_two() { assert_eq!(4, adder::add_two(2));\n} リスト11-13: adderクレートの関数の結合テスト コードの頂点にextern crate adderを追記しましたが、これは単体テストでは必要なかったものです。 理由は、testsディレクトリのテストはそれぞれ個別のクレートであるため、 各々ライブラリをインポートする必要があるためです。 tests/integration_test.rs のどんなコードも#[cfg(test)]で注釈する必要はありません。 Cargoはtestsディレクトリを特別に扱い、cargo testを走らせた時にのみこのディレクトリのファイルをコンパイルするのです。 さあ、cargo testを実行してください: $ cargo test Compiling adder v0.1.0 (file:///projects/adder) Finished dev [unoptimized + debuginfo] target(s) in 0.31 secs Running target/debug/deps/adder-abcabcabc running 1 test\ntest tests::internal ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out Running target/debug/deps/integration_test-ce99bcc2479f4607 running 1 test\ntest it_adds_two ... 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 3つの区域の出力が単体テスト、結合テスト、ドックテストを含んでいます。単体テスト用の最初の区域は、 今まで見てきたものと同じです: 各単体テストに1行(リスト11-12で追加したinternalという名前のもの)と、 単体テストのサマリー行です。 結合テストの区域は、 Running target/debug/deps/integration-test-ce99bcc2479f4607という行で始まっています(最後のハッシュはあなたの出力とは違うでしょう)。 次に、この結合テストの各テスト関数用の行があり、Doc-tests adder区域が始まる直前に、 結合テストの結果用のサマリー行があります。 単体テスト関数を追加することで単体テスト区域のテスト結果の行が増えたように、 作成した結合テストファイルにテスト関数を追加することでそのファイルの区域に結果の行が増えることになります。 結合テストファイルはそれぞれ独自の区域があるため、 tests ディレクトリにさらにファイルを追加すれば、 結合テストの区域が増えることになるでしょう。 それでも、テスト関数の名前を引数としてcargo testに指定することで、特定の結合テスト関数を走らせることができます。 特定の結合テストファイルにあるテストを全て走らせるには、cargo testに--test引数、 その後にファイル名を続けて使用してください: $ cargo test --test integration_test Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs Running target/debug/integration_test-952a27e0126bb565 running 1 test\ntest it_adds_two ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out このコマンドは、 tests/integration_test.rs ファイルにあるテストのみを実行します。 結合テスト内のサブモジュール 結合テストを追加するにつれて、 tests ディレクトリに2つ以上のファイルを作成して体系化したくなるかもしれません; 例えば、テスト対象となる機能でテスト関数をグループ化することができます。前述したように、 tests ディレクトリの各ファイルは、個別のクレートとしてコンパイルされます。 各結合テストファイルをそれ自身のクレートとして扱うと、 エンドユーザがあなたのクレートを使用するかのように個別のスコープを生成するのに役立ちます。 ですが、これは tests ディレクトリのファイルが、コードをモジュールとファイルに分ける方法に関して第7章で学んだように、 src のファイルとは同じ振る舞いを共有しないことを意味します。 tests ディレクトリのファイルの異なる振る舞いは、複数の結合テストファイルで役に立ちそうなヘルパー関数ができ、 第7章の「モジュールを別のファイルに移動する」節の手順に従って共通モジュールに抽出しようとした時に最も気付きやすくなります。 例えば、 tests/common.rs を作成し、そこにsetupという名前の関数を配置したら、 複数のテストファイルの複数のテスト関数から呼び出したいsetupに何らかのコードを追加することができます: ファイル名: tests/common.rs pub fn setup() { // ここにライブラリテスト固有のコードが来る // setup code specific to your library's tests would go here\n} 再度テストを実行すると、 common.rs ファイルは何もテスト関数を含んだり、setup関数をどこかから呼んだりしてないのに、 テスト出力に common.rs 用の区域が見えるでしょう。 running 1 test\ntest tests::internal ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out Running target/debug/deps/common-b8b07b6f1be2db70 running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out Running target/debug/deps/integration_test-d993c68b431d39df running 1 test\ntest it_adds_two ... 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 commonがrunning 0 testsとテスト結果に表示されるのは、望んだ結果ではありません。 ただ単に他の結合テストファイルと何らかのコードを共有したかっただけです。 commonがテスト出力に出現するのを防ぐには、 tests/common.rs を作成する代わりに、 tests/common/mod.rs を作成します。第7章の「モジュールファイルシステムの規則」節において、 module_name/mod.rs という命名規則をサブモジュールのあるモジュールのファイルに使用しました。 ここではcommonにサブモジュールはありませんが、 このように命名することでコンパイラにcommonモジュールを結合テストファイルとして扱わないように指示します。 setup関数のコードを tests/common/mod.rs に移動し、 tests/common.rs ファイルを削除すると、 テスト出力に区域はもう表示されなくなります。 tests ディレクトリのサブディレクトリ内のファイルは個別クレートとしてコンパイルされたり、 テスト出力に区域が表示されることがないのです。 tests/common/mod.rs を作成した後、それをどの結合テストファイルからもモジュールとして使用することができます。 こちらは、 tests/integration_test.rs 内のit_adds_twoテストからsetup関数を呼び出す例です: ファイル名: tests/integration_test.rs extern crate adder; mod common; #[test]\nfn it_adds_two() { common::setup(); assert_eq!(4, adder::add_two(2));\n} mod common;という宣言は、リスト7-4で模擬したモジュール宣言と同じであることに注意してください。 それから、テスト関数内でcommon::setup()関数を呼び出すことができます。 バイナリクレート用の結合テスト もしもプロジェクトが src/main.rs ファイルのみを含み、 src/lib.rs ファイルを持たないバイナリクレートだったら、 tests ディレクトリに結合テストを作成し、 extern crateを使用して src/main.rs ファイルに定義された関数をインポートすることはできません。 ライブラリクレートのみが、他のクレートが呼び出して使用できる関数を晒せるのです; バイナリクレートはそれ単体で実行することを意味しています。 これは、バイナリを提供するRustのプロジェクトに、 src/lib.rs ファイルに存在するロジックを呼び出す単純な src/main.rs ファイルがある一因になっています。 この構造を使用して結合テストは、extern crateを使用して重要な機能を用いることでライブラリクレートをテストすることが できます 。 この重要な機能が動作すれば、 src/main.rs ファイルの少量のコードも動作し、その少量のコードはテストする必要がないわけです。","breadcrumbs":"自動テストを書く » 結合テスト","id":"207","title":"結合テスト"},"208":{"body":"Rustのテスト機能は、変更を加えた後でさえ想定通りにコードが機能し続けることを保証して、 コードが機能すべき方法を指定する手段を提供します。単体テストはライブラリの異なる部分を個別に用い、 非公開の実装詳細をテストすることができます。結合テストは、ライブラリのいろんな部分が共同で正常に動作することを確認し、 ライブラリの公開APIを使用して外部コードが使用するのと同じ方法でコードをテストします。 Rustの型システムと所有権ルールにより防がれるバグの種類もあるものの、それでもテストは、 コードが振る舞うと予想される方法に関するロジックのバグを減らすのに重要なのです。 この章と以前の章で学んだ知識を結集して、とあるプロジェクトに取り掛かりましょう!","breadcrumbs":"自動テストを書く » まとめ","id":"208","title":"まとめ"},"209":{"body":"この章は、ここまでに学んできた多くのスキルを思い出すきっかけであり、もういくつか標準ライブラリの機能も探究します。 ファイルやコマンドラインの入出力と相互作用するコマンドラインツールを構築し、 今やあなたの支配下にあるRustの概念の一部を練習していきます。 Rustの速度、安全性、単バイナリ出力、クロスプラットフォームサポートにより、コマンドラインツールを作るのにふさわしい言語なので、 このプロジェクトでは、独自の伝統的なコマンドラインツールのgrep( g lobally search a r egular e xpression and p rint: 正規表現をグローバルで検索し表示する)を作成していきます。最も単純な使用法では、 grepは指定したファイルから指定した文字列を検索します。そうするには、 grepは引数としてファイル名と文字列を受け取ります。それからファイルを読み込んでそのファイル内で文字列引数を含む行を探し、 検索した行を出力するのです。 その過程で、多くのコマンドラインツールが使用している端末の機能を使用させる方法を示します。 環境変数の値を読み取ってユーザがこのツールの振る舞いを設定できるようにします。また、 標準出力(stdout)の代わりに、標準エラーに出力(stderr)するので、例えば、 ユーザはエラーメッセージは画面上で確認しつつ、成功した出力はファイルにリダイレクトできます。 Rustコミュニティのあるメンバであるアンドリュー・ガラント(Andrew Gallant)が既に全機能装備の非常に高速なgrep、 ripgrepと呼ばれるものを作成しました。比較対象として、我々のgrepはとても単純ですが、 この章により、ripgrepのような現実世界のプロジェクトを理解するのに必要な背景知識の一部を身に付けられるでしょう。 このgrepプロジェクトは、ここまでに学んできた多くの概念を集結させます: コードを体系化する(モジュール、第7章で学んだことを使用) ベクタと文字列を使用する(コレクション、第8章) エラーを処理する(第9章) 適切な箇所でトレイトとライフタイムを使用する(第10章) テストを記述する(第11章) さらに、クロージャ、イテレータ、トレイトオブジェクトなど、第13章、17章で詳しく講義するものもちょっとだけ紹介します。","breadcrumbs":"入出力プロジェクト: コマンドラインプログラムを構築する","id":"209","title":"入出力プロジェクト: コマンドラインプログラムを構築する"},"21":{"body":"Rustコードを格納するディレクトリを作ることから始めましょう。Rustにとって、コードがどこにあるかは問題ではありませんが、 この本の練習とプロジェクトのために、ホームディレクトリに projects ディレクトリを作成してプロジェクトを全てそこに保管することを推奨します。 端末を開いて以下のコマンドを入力し、 projects ディレクトリと、 projects ディレクトリ内にHello, world!プロジェクトのディレクトリを作成してください。 LinuxとmacOSなら、こう入力してください: $ mkdir ~/projects\n$ cd ~/projects\n$ mkdir hello_world\n$ cd hello_world Windowsのcmdなら、こう: > mkdir \"%USERPROFILE%\\projects\"\n> cd /d \"%USERPROFILE%\\projects\"\n> mkdir hello_world\n> cd hello_world WindowsのPowerShellなら、こう: > mkdir $env:USERPROFILE\\projects\n> cd $env:USERPROFILE\\projects\n> mkdir hello_world\n> cd hello_world","breadcrumbs":"事始め » プロジェクトのディレクトリを作成する","id":"21","title":"プロジェクトのディレクトリを作成する"},"210":{"body":"いつものように、cargo newで新しいプロジェクトを作りましょう。プロジェクトをminigrepと名付けて、 既に自分のシステムに存在するかもしれないgrepツールと区別しましょう。 最初の仕事は、minigrepを二つの引数を受け付けるようにすることです: ファイル名と検索する文字列ですね。 つまり、cargo runで検索文字列と検索を行うファイルへのパスと共にプログラムを実行できるようになりたいということです。 こんな感じにね: $ cargo run searchstring example-filename.txt 今現在は、cargo newで生成されたプログラムは、与えた引数を処理できません。 Crates.io に存在する既存のライブラリには、 コマンドライン引数を受け付けるプログラムを書く手助けをしてくれるものもありますが、ちょうどこの概念を学んでいる最中なので、 この能力を自分で実装しましょう。","breadcrumbs":"入出力プロジェクト:コマンドラインプログラムを構築する » コマンドライン引数を受け付ける","id":"210","title":"コマンドライン引数を受け付ける"},"211":{"body":"minigrepが渡したコマンドライン引数の値を読み取れるようにするために、Rustの標準ライブラリで提供されている関数が必要になり、 それは、std::env::argsです。この関数は、minigrepに与えられたコマンドライン引数の イテレータ を返します。 イテレータについてはまだ議論していません(完全には第13章で講義します)が、とりあえずイテレータに関しては、 2つの詳細のみ知っていればいいです: イテレータは一連の値を生成することと、イテレータに対してcollect関数を呼び出し、 イテレータが生成する要素全部を含むベクタなどのコレクションに変えられることです。 リスト12-1のコードを使用してminigrepプログラムに渡されたあらゆるコマンドライン引数を読み取れるようにし、 それからその値をベクタとして集結させてください。 ファイル名: src/main.rs use std::env; fn main() { let args: Vec = env::args().collect(); println!(\"{:?}\", args);\n} リスト12-1: コマンドライン引数をベクタに集結させ、出力する まず、std::envモジュールをuse文でスコープに導入したので、args関数が使用できます。 std::env::args関数は、2レベルモジュールがネストされていることに注目してください。 第7章で議論したように、希望の関数が2モジュール以上ネストされている場合、 関数ではなく親モジュールをスコープに導入するのが因習的です。そうすることで、 std::envから別の関数も容易に使用することができます。また、 use std::env::argsを追記し、関数をargsとするだけで呼び出すのに比べて曖昧でもありません。 というのも、argsは現在のモジュールに定義されている関数と容易に見間違えられるかもしれないからです。","breadcrumbs":"入出力プロジェクト:コマンドラインプログラムを構築する » 引数の値を読み取る","id":"211","title":"引数の値を読み取る"},"212":{"body":"引数のどれかが不正なユニコードを含んでいたら、std::env::argsはパニックすることに注意してください。 プログラムが不正なユニコードを含む引数を受け付ける必要があるなら、代わりにstd::env::args_osを使用してください。 この関数は、String値ではなく、OsString値を生成するイテレータを返します。ここでは、 簡潔性のためにstd::env::argsを使うことを選択しました。 なぜなら、OsString値はプラットフォームごとに異なり、String値に比べて取り扱いが煩雑だからです。 mainの最初の行でenv::argsを呼び出し、そして即座にcollectを使用して、 イテレータをイテレータが生成する値全てを含むベクタに変換しています。 collect関数を使用して多くの種類のコレクションを生成することができるので、 argsの型を明示的に注釈して文字列のベクタが欲しいのだと指定しています。Rustにおいて、 型を注釈しなければならない頻度は非常に少ないのですが、collectはよく確かに注釈が必要になる一つの関数なのです。 コンパイラには、あなたが欲しているコレクションの種類が推論できないからです。 最後に、デバッグ整形機の:?を使用してベクタを出力しています。引数なしでコードを走らせてみて、 それから引数二つで試してみましょう: $ cargo run\n--snip--\n[\"target/debug/minigrep\"] $ cargo run needle haystack\n--snip--\n[\"target/debug/minigrep\", \"needle\", \"haystack\"] ベクタの最初の値は\"target/debug/minigrep\"であることに注目してください。これはバイナリの名前です。 これはCの引数リストの振る舞いと合致し、実行時に呼び出された名前をプログラムに使わせてくれるわけです。 メッセージで出力したり、プログラムを起動するのに使用されたコマンドラインエイリアスによってプログラムの振る舞いを変えたい場合に、 プログラム名にアクセスするのにしばしば便利です。ですが、この章の目的には、これを無視し、必要な二つの引数のみを保存します。","breadcrumbs":"入出力プロジェクト:コマンドラインプログラムを構築する » args関数と不正なユニコード","id":"212","title":"args関数と不正なユニコード"},"213":{"body":"引数のベクタの値を出力すると、プログラムはコマンドライン引数として指定された値にアクセスできることが説明されました。 さて、プログラムの残りを通して使用できるように、二つの引数の値を変数に保存する必要があります。 それをしているのがリスト12-2です。 ファイル名: src/main.rs use std::env; fn main() { let args: Vec = env::args().collect(); let query = &args[1]; let filename = &args[2]; // {}を探しています println!(\"Searching for {}\", query); // {}というファイルの中 println!(\"In file {}\", filename);\n} リスト12-2: クエリ引数とファイル名引数を保持する変数を生成 ベクタを出力した時に確認したように、プログラム名がベクタの最初の値、args[0]を占めているので、 添え字1から始めます。minigrepが取る最初の引数は、検索する文字列なので、 最初の引数への参照を変数queryに置きました。2番目の引数はファイル名でしょうから、 2番目の引数への参照は変数filenameに置きました。 一時的にこれらの変数の値を出力して、コードが意図通りに動いていることを証明しています。 再度このプログラムをtestとsample.txtという引数で実行しましょう: $ cargo run test sample.txt Compiling minigrep v0.1.0 (file:///projects/minigrep) Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs Running `target/debug/minigrep test sample.txt`\nSearching for test\nIn file sample.txt 素晴らしい、プログラムは動作しています!必要な引数の値が、正しい変数に保存されています。後ほど、 何らかのエラー処理を加えて、ユーザが引数を提供しなかった場合など、可能性のある特定のエラー状況に対処します; 今は、そのような状況はないものとし、代わりにファイル読み取り能力を追加することに取り組みます。","breadcrumbs":"入出力プロジェクト:コマンドラインプログラムを構築する » 引数の値を変数に保存する","id":"213","title":"引数の値を変数に保存する"},"214":{"body":"では、filenameコマンドライン引数で指定されたファイルを読み込む機能を追加しましょう。 まず、テスト実行するためのサンプルファイルが必要ですね: minigrepが動作していることを確かめるために使用するのに最適なファイルは、 複数行にわたって同じ単語の繰り返しのある少量のテキストです。リスト12-3は、 うまくいくであろうエミリー・ディキンソン(Emily Dickinson)の詩です! プロジェクトのルート階層に poem.txt というファイルを作成し、この詩「私は誰でもない!あなたは誰?」を入力してください。 ファイル名: poem.txt I'm nobody! Who are you?\nAre you nobody, too?\nThen there's a pair of us - don't tell!\nThey'd banish us, you know. How dreary to be somebody!\nHow public, like a frog\nTo tell your name the livelong day\nTo an admiring bog! 私は誰でもない!あなたは誰?\nあなたも誰でもないの?\nなら、私たちは組だね、何も言わないで!\nあの人たちは、私たちを追放するでしょう。わかりますよね? 誰かでいるなんて侘しいじゃない!\nカエルみたいで公すぎるじゃない。\n自分の名を長い1日に告げるのなんて。\n感服するような沼地にね! リスト12-3: エミリー・ディキンソンの詩は、いいテストケースになる テキストを適当な場所に置いて、 src/main.rs を編集し、ファイルを開くコードを追加してください。 リスト12-4に示したようにですね。 ファイル名: src/main.rs use std::env;\nuse std::fs::File;\nuse std::io::prelude::*; fn main() {\n# let args: Vec = env::args().collect();\n#\n# let query = &args[1];\n# let filename = &args[2];\n#\n# println!(\"Searching for {}\", query); // --snip-- println!(\"In file {}\", filename); // ファイルが見つかりませんでした let mut f = File::open(filename).expect(\"file not found\"); let mut contents = String::new(); f.read_to_string(&mut contents) // ファイルの読み込み中に問題がありました .expect(\"something went wrong reading the file\"); // テキストは\\n{}です println!(\"With text:\\n{}\", contents);\n} リスト12-4: 第2引数で指定されたファイルの中身を読み込む 最初に、もう何個かuse文を追記して、標準ライブラリの関係のある箇所を持ってきています: ファイルを扱うのにstd::fs::Fileが必要ですし、 std::io::prelude::*はファイル入出力を含む入出力処理をするのに有用なトレイトを色々含んでいます。 言語が一般的な初期化処理で特定の型や関数を自動的にスコープに導入するように、 std::ioモジュールにはそれ独自の共通の型や関数の初期化処理があり、入出力を行う際に必要になるわけです。 標準の初期化処理とは異なり、std::ioの初期化処理には明示的にuse文を加えなければなりません。 mainに3文を追記しました: 一つ目が、File::open関数を呼んでfilename変数の値に渡して、 ファイルへの可変なハンドルを得る処理です。二つ目が、contentsという名の変数を生成して、 可変で空のStringを割り当てる処理です。この変数が、ファイル読み込み後に中身を保持します。 三つ目が、ファイルハンドルに対してread_to_stringを呼び出し、引数としてcontentsへの可変参照を渡す処理です。 それらの行の後に、今回もファイル読み込み後にcontentsの値を出力する一時的なprintln!文を追記したので、 ここまでプログラムがきちんと動作していることを確認できます。 第1コマンドライン引数には適当な文字列(まだ検索する箇所は実装してませんからね)を、第2引数に poem.txt ファイルを入れて、 このコードを実行しましょう: $ cargo run the poem.txt Compiling minigrep v0.1.0 (file:///projects/minigrep) Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs Running `target/debug/minigrep the poem.txt`\nSearching for the\nIn file poem.txt\nWith text:\nI'm nobody! Who are you?\nAre you nobody, too?\nThen there's a pair of us - don't tell!\nThey'd banish us, you know. How dreary to be somebody!\nHow public, like a frog\nTo tell your name the livelong day\nTo an admiring bog! 素晴らしい!コードがファイルの中身を読み込み、出力するようになりました。しかし、このコードにはいくつか欠陥があります。 main関数が複数の責任を受け持っています: 一般に、各関数がただ一つの責任だけを持つようになれば、 関数は明確かつ、管理しやすくなります。もう一つの問題点は、できうる限りのエラー処理を怠っていることです。 まだプログラムが小規模なので、これらの欠陥は大きな問題にはなりませんが、プログラムが大規模になるにつれ、 それを綺麗に解消するのは困難になっていきます。プログラムを開発する際に早い段階でリファクタングを行うのは、 良い戦術です。リファクタリングするコードの量が少なければ、はるかに簡単になりますからね。次は、それを行いましょう。","breadcrumbs":"入出力プロジェクト:コマンドラインプログラムを構築する » ファイルを読み込む","id":"214","title":"ファイルを読み込む"},"215":{"body":"プログラムを改善するために、プログラムの構造と起こりうるエラーに対処する方法に関連する4つの問題を修正していきましょう。 1番目は、main関数が2つの仕事を受け持っていることです: 引数を解析し、ファイルを開いています。 このような小さな関数なら、これは、大した問題ではありませんが、main内でプログラムを巨大化させ続けたら、 main関数が扱う個別の仕事の数も増えていきます。関数が責任を受け持つごとに、 正しいことを確認しにくくなり、テストも行いづらくなり、機能を壊さずに変更するのも困難になっていきます。 機能を小分けして、各関数が1つの仕事のみに責任を持つようにするのが最善です。 この問題は、2番目の問題にも結びついています: queryとfilenameはプログラムの設定用変数ですが、 fやcontentsといった変数は、プログラムのロジックを担っています。mainが長くなるほど、 スコープに入れるべき変数も増えます。そして、スコープにある変数が増えれば、各々の目的を追うのも大変になるわけです。 設定用変数を一つの構造に押し込め、目的を明瞭化するのが最善です。 3番目の問題は、ファイルを開き損ねた時にexpectを使ってエラーメッセージを出力しているのに、 エラーメッセージがファイルが見つかりませんでしたとしか表示しないことです。 ファイルを開く行為は、ファイルが存在しない以外にもいろんな方法で失敗することがあります: 例えば、ファイルは存在するかもしれないけれど、開く権限がないかもしれないなどです。 現時点では、そのような状況になった時、「ファイルが見つかりませんでした」というエラーメッセージを出力し、 これはユーザに間違った情報を与えるのです。 4番目は、異なるエラーを処理するのにexpectを繰り返し使用しているので、ユーザが十分な数の引数を渡さずにプログラムを起動した時に、 問題を明確に説明しない「範囲外アクセス(index out of bounds)」というエラーがRustから得られることです。 エラー処理のコードが全て1箇所に存在し、将来エラー処理ロジックが変更になった時に、 メンテナンス者が1箇所のコードのみを考慮すればいいようにするのが最善でしょう。 エラー処理コードが1箇所にあれば、エンドユーザにとって意味のあるメッセージを出力していることを確認することにもつながります。 プロジェクトをリファクタリングして、これら4つの問題を扱いましょう。","breadcrumbs":"入出力プロジェクト:コマンドラインプログラムを構築する » リファクタリングしてモジュール性とエラー処理を向上させる","id":"215","title":"リファクタリングしてモジュール性とエラー処理を向上させる"},"216":{"body":"main関数に複数の仕事の責任を割り当てるという構造上の問題は、多くのバイナリプロジェクトでありふれています。 結果として、mainが肥大化し始めた際にバイナリプログラムの個別の責任を分割するためにガイドラインとして活用できる工程をRustコミニュティは、 開発しました。この工程は、以下のような手順になっています: プログラムを main.rs と lib.rs に分け、ロジックを lib.rs に移動する。 コマンドライン引数の解析ロジックが小規模な限り、 main.rs に置いても良い。 コマンドライン引数の解析ロジックが複雑化の様相を呈し始めたら、 main.rs から抽出して lib.rs に移動する。 この工程の後にmain関数に残る責任は以下に限定される: 引数の値でコマンドライン引数の解析ロジックを呼び出す 他のあらゆる設定を行う lib.rs のrun関数を呼び出す runがエラーを返した時に処理する このパターンは、責任の分離についてです: main.rs はプログラムの実行を行い、 そして、 lib.rs が手にある仕事のロジック全てを扱います。main関数を直接テストすることはできないので、 この構造により、プログラムのロジック全てを lib.rs の関数に移すことでテストできるようになります。 main.rs に残る唯一のコードは、読めばその正当性が評価できるだけ小規模になるでしょう。 この工程に従って、プログラムのやり直しをしましょう。 引数解析器を抽出する 引数解析の機能をmainが呼び出す関数に抽出して、コマンドライン引数解析ロジックを src/lib.rs に移動する準備をします。 リスト12-5に新しい関数parse_configを呼び出すmainの冒頭部を示し、 この新しい関数は今だけ src/main.rs に定義します。 ファイル名: src/main.rs fn main() { let args: Vec = env::args().collect(); let (query, filename) = parse_config(&args); // --snip--\n} fn parse_config(args: &[String]) -> (&str, &str) { let query = &args[1]; let filename = &args[2]; (query, filename)\n} リスト12-5: mainからparse_config関数を抽出する それでもまだ、コマンドライン引数をベクタに集結させていますが、main関数内で引数の値の添え字1を変数queryに、 添え字2を変数filenameに代入する代わりに、ベクタ全体をparse_config関数に渡しています。 そして、parse_config関数にはどの引数がどの変数に入り、それらの値をmainに返すというロジックが存在します。 まだmain内にqueryとfilenameという変数を生成していますが、もうmainは、 コマンドライン引数と変数がどう対応するかを決定する責任は持ちません。 このやり直しは、私たちの小規模なプログラムにはやりすぎに思えるかもしれませんが、 少しずつ段階的にリファクタリングしているのです。この変更後、プログラムを再度実行して、 引数解析がまだ動作していることを実証してください。問題が発生した時に原因を特定する助けにするために頻繁に進捗を確認するのはいいことです。 設定値をまとめる もう少しparse_config関数を改善することができます。現時点では、タプルを返していますが、 即座にタプルを分解して再度個別の値にしています。これは、正しい抽象化をまだできていないかもしれない兆候です。 まだ改善の余地があると示してくれる他の徴候は、parse_configのconfigの部分であり、 返却している二つの値は関係があり、一つの設定値の一部にどちらもなることを暗示しています。 現状では、一つのタプルにまとめていること以外、この意味をデータの構造に載せていません; この二つの値を1構造体に置き換え、構造体のフィールドそれぞれに意味のある名前をつけることもできるでしょう。 そうすることで将来このコードのメンテナンス者が、異なる値が相互に関係する仕方や、目的を理解しやすくできるでしょう。 注釈: この複雑型(complex type)がより適切な時に組み込みの値を使うアンチパターンを、 primitive obsession (訳注: 初めて聞いた表現。 組み込み型強迫観念 といったところだろうか)と呼ぶ人もいます。 リスト12-6は、parse_config関数の改善を示しています。 ファイル名: src/main.rs # use std::env;\n# use std::fs::File;\n#\nfn main() { let args: Vec = env::args().collect(); let config = parse_config(&args); println!(\"Searching for {}\", config.query); println!(\"In file {}\", config.filename); let mut f = File::open(config.filename).expect(\"file not found\"); // --snip--\n} struct Config { query: String, filename: String,\n} fn parse_config(args: &[String]) -> Config { let query = args[1].clone(); let filename = args[2].clone(); Config { query, filename }\n} リスト12-6: parse_configをリファクタリングしてConfig構造体のインスタンスを返す queryとfilenameというフィールドを持つよう定義されたConfigという構造体を追加しました。 parse_configのシグニチャは、これでConfig値を返すと示すようになりました。parse_configの本体では、 以前はargsのString値を参照する文字列スライスを返していましたが、 今では所有するString値を含むようにConfigを定義しています。mainのargs変数は引数値の所有者であり、 parse_config関数だけに借用させていますが、これはConfigがargsの値の所有権を奪おうとしたら、 Rustの借用規則に違反してしまうことを意味します。 Stringのデータは、多くの異なる手法で管理できますが、最も単純だけれどもどこか非効率的な手段は、 値に対してcloneメソッドを呼び出すことです。これにより、Configインスタンスが所有するデータの総コピーが生成されるので、 文字列データへの参照を保持するよりも時間とメモリを消費します。ですが、データをクローンすることで、 コードがとても素直にもなります。というのも、参照のライフタイムを管理する必要がないからです。 つまり、この場面において、少々のパフォーマンスを犠牲にして単純性を得るのは、価値のある代償です。","breadcrumbs":"入出力プロジェクト:コマンドラインプログラムを構築する » バイナリプロジェクトの責任の分離","id":"216","title":"バイナリプロジェクトの責任の分離"},"217":{"body":"実行時コストのためにcloneを使用して所有権問題を解消するのを避ける傾向が多くのRustaceanにあります。 第13章で、この種の状況においてより効率的なメソッドの使用法を学ぶでしょう。ですがとりあえずは、 これらのコピーをするのは1回だけですし、ファイル名とクエリ文字列は非常に小さなものなので、 いくつかの文字列をコピーして進捗するのは良しとしましょう。最初の通り道でコードを究極的に効率化しようとするよりも、 ちょっと非効率的でも動くプログラムを用意する方がいいでしょう。もっとRustの経験を積めば、 最も効率的な解決法から開始することも簡単になるでしょうが、今は、cloneを呼び出すことは完璧に受け入れられることです。 mainを更新したので、parse_configから返されたConfigのインスタンスをconfigという変数に置くようになり、 以前は個別のqueryとfilename変数を使用していたコードを更新したので、代わりにConfig構造体のフィールドを使用するようになりました。 これでコードはqueryとfilenameが関連していることと、その目的がプログラムの振る舞い方を設定するということをより明確に伝えます。 これらの値を使用するあらゆるコードは、configインスタンスの目的の名前を冠したフィールドにそれらを発見することを把握しています。 Configのコンストラクタを作成する ここまでで、コマンドライン引数を解析する責任を負ったロジックをmainから抽出し、parse_config関数に配置しました。 そうすることでqueryとfilenameの値が関連し、その関係性がコードに載っていることを確認する助けになりました。 それからConfig構造体を追加してqueryとfilenameの関係する目的を名前付けし、 構造体のフィールド名としてparse_config関数からその値の名前を返すことができています。 したがって、今やparse_config関数の目的はConfigインスタンスを生成することになったので、 parse_configをただの関数からConfig構造体に紐づくnewという関数に変えることができます。 この変更を行うことで、コードがより慣用的になります。Stringなどの標準ライブラリの型のインスタンスを、 String::newを呼び出すことで生成できます。同様に、parse_configをConfigに紐づくnew関数に変えれば、 Config::newを呼び出すことでConfigのインスタンスを生成できるようになります。リスト12-7が、 行う必要のある変更を示しています。 ファイル名: src/main.rs # use std::env;\n#\nfn main() { let args: Vec = env::args().collect(); let config = Config::new(&args); // --snip--\n} # struct Config {\n# query: String,\n# filename: String,\n# }\n#\n// --snip-- impl Config { fn new(args: &[String]) -> Config { let query = args[1].clone(); let filename = args[2].clone(); Config { query, filename } }\n} リスト12-7: parse_configをConfig::newに変える parse_configを呼び出していたmainを代わりにConfig::newを呼び出すように更新しました。 parse_configの名前をnewに変え、implブロックに入れ込んだので、new関数とConfigが紐づくようになりました。 再度このコードをコンパイルしてみて、動作することを確かめてください。","breadcrumbs":"入出力プロジェクト:コマンドラインプログラムを構築する » cloneを使用する代償","id":"217","title":"cloneを使用する代償"},"218":{"body":"さて、エラー処理の修正に取り掛かりましょう。ベクタが2個以下の要素しか含んでいないときにargsベクタの添え字1か2にアクセスしようとすると、 プログラムがパニックすることを思い出してください。試しに引数なしでプログラムを実行してください。すると、こんな感じになります: $ cargo run Compiling minigrep v0.1.0 (file:///projects/minigrep) Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs Running `target/debug/minigrep`\nthread 'main' panicked at 'index out of bounds: the len is 1\nbut the index is 1', src/main.rs:29:21\n(スレッド'main'は、「境界外アクセス: 長さは1なのに添え字も1です」でパニックしました)\nnote: Run with `RUST_BACKTRACE=1` for a backtrace. 境界外アクセス: 長さは1なのに添え字も1ですという行は、プログラマ向けのエラーメッセージです。 エンドユーザが起きたことと代わりにすべきことを理解する手助けにはならないでしょう。これを今修正しましょう。 エラーメッセージを改善する リスト12-8で、new関数に、添え字1と2にアクセスする前にスライスが十分長いことを実証するチェックを追加しています。 スライスの長さが十分でなければ、プログラムはパニックし、境界外インデックスよりもいいエラーメッセージを表示します。 ファイル名: src/main.rs // --snip--\nfn new(args: &[String]) -> Config { if args.len() < 3 { // 引数の数が足りません panic!(\"not enough arguments\"); } // --snip-- リスト12-8: 引数の数のチェックを追加する このコードは、リスト9-9で記述したvalue引数が正常な値の範囲外だった時にpanic!を呼び出したGuess::new関数と似ています。 ここでは、値の範囲を確かめる代わりに、argsの長さが少なくとも3であることを確かめていて、 関数の残りの部分は、この条件が満たされているという前提のもとで処理を行うことができます。 argsに2要素以下しかなければ、この条件は真になり、panic!マクロを呼び出して、即座にプログラムを終了させます。 では、newのこの追加の数行がある状態で、再度引数なしでプログラムを走らせ、エラーがどんな見た目か確かめましょう: $ cargo run Compiling minigrep v0.1.0 (file:///projects/minigrep) Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs Running `target/debug/minigrep`\nthread 'main' panicked at 'not enough arguments', src/main.rs:30:12\n(スレッド'main'は「引数が足りません」でパニックしました)\nnote: Run with `RUST_BACKTRACE=1` for a backtrace. この出力の方がマシです: これでエラーメッセージが合理的になりました。ですが、 ユーザに与えたくない追加の情報も含まれてしまっています。おそらく、 ここではリスト9-9で使用したテクニックを使用するのは最善ではありません: panic!の呼び出しは、第9章で議論したように、使用の問題よりもプログラミング上の問題により適しています。 代わりに、第9章で学んだもう一つのテクニックを使用することができます。成功か失敗かを示唆するResultを返すことです。 panic!を呼び出す代わりにnewからResultを返す 代わりに、成功時にはConfigインスタンスを含み、エラー時には問題に言及するResult値を返すことができます。 Config::newがmainと対話する時、Result型を使用して問題があったと信号を送ることができます。 それからmainを変更して、panic!呼び出しが引き起こしていたthread 'main'とRUST_BACKTRACEに関する周囲のテキストがない、 ユーザ向けのより実用的なエラーにErr列挙子を変換することができます。 リスト12-9は、Config::newの戻り値に必要な変更とResultを返すのに必要な関数の本体を示しています。 mainも更新するまで、これはコンパイルできないことに注意してください。その更新は次のリストで行います。 ファイル名: src/main.rs impl Config { fn new(args: &[String]) -> Result { if args.len() < 3 { return Err(\"not enough arguments\"); } let query = args[1].clone(); let filename = args[2].clone(); Ok(Config { query, filename }) }\n} リスト12-9: Config::newからResultを返却する new関数は、これで、成功時にはConfigインスタンスを、エラー時には&'static strを伴うResultを返すようになりました。 第10章の「静的ライフタイム」節から&'static strは文字列リテラルの型であることを思い出してください。 これは、今はエラーメッセージの型になっています。 new関数の本体で2つ変更を行いました: 十分な数の引数をユーザが渡さなかった場合にpanic!を呼び出す代わりに、 今はErr値を返し、Config戻り値をOkに包んでいます。これらの変更により、関数が新しい型シグニチャに適合するわけです。 Config::newからErr値を返すことにより、main関数は、new関数から返ってくるResult値を処理し、 エラー時により綺麗にプロセスから抜け出すことができます。 Config::newを呼び出し、エラーを処理する エラーケースを処理し、ユーザフレンドリーなメッセージを出力するために、mainを更新して、 リスト12-10に示したようにConfig::newから返されているResultを処理する必要があります。 また、panic!からコマンドラインツールを0以外のエラーコードで抜け出す責任も奪い取り、 手作業でそれも実装します。0以外の終了コードは、 我々のプログラムを呼び出したプロセスにプログラムがエラー状態で終了したことを通知する慣習です。 ファイル名: src/main.rs use std::process; fn main() { let args: Vec = env::args().collect(); let config = Config::new(&args).unwrap_or_else(|err| { // 引数解析時に問題 println!(\"Problem parsing arguments: {}\", err); process::exit(1); }); // --snip-- リスト12-10: 新しいConfig作成に失敗したら、エラーコードで終了する このリストにおいて、以前には講義していないメソッドを使用しました: unwrap_or_elseです。 これは標準ライブラリでResultに定義されています。unwrap_or_elseを使うことで、 panic!ではない何らか独自のエラー処理を定義できるのです。このResultがOk値だったら、 このメソッドの振る舞いはunwrapに似ています: Okが包んでいる中身の値を返すのです。 しかし、値がErr値なら、このメソッドは、 クロージャ 内でコードを呼び出し、 クロージャは私たちが定義し、引数としてunwrap_or_elseに渡す匿名関数です。クロージャについては第13章で詳しく講義します。 とりあえず、unwrap_or_elseは、今回リスト12-9で追加したnot enough argumentsという静的文字列のErrの中身を、 縦棒の間に出現するerr引数のクロージャに渡していることだけ知っておく必要があります。 クロージャのコードはそれから、実行された時にerr値を使用できます。 新規use行を追加して標準ライブラリからprocessをインポートしました。クロージャ内のエラー時に走るコードは、 たった2行です: errの値を出力し、それからprocess::exitを呼び出します。process::exit関数は、 即座にプログラムを停止させ、渡された数字を終了コードとして返します。これは、リスト12-8で使用したpanic!ベースの処理と似ていますが、 もう余計な出力はされません。試しましょう: $ cargo run Compiling minigrep v0.1.0 (file:///projects/minigrep) Finished dev [unoptimized + debuginfo] target(s) in 0.48 secs Running `target/debug/minigrep`\nProblem parsing arguments: not enough arguments 素晴らしい!この出力の方が遥かにユーザに優しいです。","breadcrumbs":"入出力プロジェクト:コマンドラインプログラムを構築する » エラー処理を修正する","id":"218","title":"エラー処理を修正する"},"219":{"body":"これで設定解析のリファクタリングが終了したので、プログラムのロジックに目を向けましょう。 「バイナリプロジェクトの責任の分離」で述べたように、 現在main関数に存在する設定のセットアップやエラー処理に関わらない全てのロジックを保持することになるrunという関数を抽出します。 やり終わったら、mainは簡潔かつ視察で確かめやすくなり、他のロジック全部に対してテストを書くことができるでしょう。 リスト12-11は、抜き出したrun関数を示しています。今は少しずつ段階的に関数を抽出する改善を行っています。 それでも、 src/main.rs に関数を定義していきます。 ファイル名: src/main.rs fn main() { // --snip-- println!(\"Searching for {}\", config.query); println!(\"In file {}\", config.filename); run(config);\n} fn run(config: Config) { let mut f = File::open(config.filename).expect(\"file not found\"); let mut contents = String::new(); f.read_to_string(&mut contents) .expect(\"something went wrong reading the file\"); println!(\"With text:\\n{}\", contents);\n} // --snip-- リスト12-11: 残りのプログラムロジックを含むrun関数を抽出する これでrun関数は、ファイル読み込みから始まるmain関数の残りのロジック全てを含むようになりました。 このrun関数は、引数にConfigインスタンスを取ります。 run関数からエラーを返す 残りのプログラムロジックがrun関数に隔離されたので、リスト12-9のConfig::newのように、 エラー処理を改善することができます。expectを呼び出してプログラムにパニックさせる代わりに、 run関数は、何か問題が起きた時にResultを返します。これにより、 さらにエラー処理周りのロジックをユーザに優しい形でmainに統合することができます。 リスト12-12にシグニチャとrun本体に必要な変更を示しています。 ファイル名: src/main.rs use std::error::Error; // --snip-- fn run(config: Config) -> Result<(), Box> { let mut f = File::open(config.filename)?; let mut contents = String::new(); f.read_to_string(&mut contents)?; println!(\"With text:\\n{}\", contents); Ok(())\n} リスト12-12: run関数を変更してResultを返す ここでは、3つの大きな変更を行いました。まず、run関数の戻り値をResult<(), Box>に変えました。 この関数は、以前はユニット型、()を返していて、それをOkの場合に返される値として残しました。 エラー型については、 トレイトオブジェクト のBoxを使用しました(同時に冒頭でuse文により、 std::error::Errorをスコープに導入しています)。トレイトオブジェクトについては、第17章で講義します。 とりあえず、Boxは、関数がErrorトレイトを実装する型を返すことを意味しますが、 戻り値の型を具体的に指定しなくても良いことを知っておいてください。これにより、 エラーケースによって異なる型のエラー値を返す柔軟性を得ます。 2番目に、expectの呼び出しよりも?演算子を選択して取り除きました。第9章で語りましたね。 エラーでパニックするのではなく、?演算子は呼び出し元が処理できるように、現在の関数からエラー値を返します。 3番目に、run関数は今、成功時にOk値を返すようになりました。run関数の成功型は、 シグニチャで()と定義したので、ユニット型の値をOk値に包む必要があります。 最初は、このOk(())という記法は奇妙に見えるかもしれませんが、このように()を使うことは、 runを副作用のためだけに呼び出していると示唆する慣習的な方法です; 必要な値は返しません。 このコードを実行すると、コンパイルは通るものの、警告が表示されるでしょう: warning: unused `std::result::Result` which must be used\n(警告: 使用されなければならない`std::result::Result`が未使用です) --> src/main.rs:18:5 |\n18 | run(config); | ^^^^^^^^^^^^\n= note: #[warn(unused_must_use)] on by default コンパイラは、コードがResult値を無視していると教えてくれて、このResult値は、 エラーが発生したと示唆しているかもしれません。しかし、エラーがあったか確認するつもりはありませんが、 コンパイラは、ここにエラー処理コードを書くつもりだったんじゃないかと思い出させてくれています! 今、その問題を改修しましょう。 mainでrunから返ってきたエラーを処理する リスト12-10のConfig::newに対して行った方法に似たテクニックを使用してエラーを確認し、扱いますが、 少し違いがあります: ファイル名: src/main.rs fn main() { // --snip-- println!(\"Searching for {}\", config.query); println!(\"In file {}\", config.filename); if let Err(e) = run(config) { println!(\"Application error: {}\", e); process::exit(1); }\n} unwrap_or_elseではなく、if letでrunがErr値を返したかどうかを確認し、そうならprocess::exit(1)を呼び出しています。 run関数は、Config::newがConfigインスタンスを返すのと同じようにunwrapしたい値を返すことはありません。 runは成功時に()を返すので、エラーを検知することにのみ興味があり、()でしかないので、 unwrap_or_elseに包まれた値を返してもらう必要はないのです。 if letとunwrap_or_else関数の中身はどちらも同じです: エラーを出力して終了します。","breadcrumbs":"入出力プロジェクト:コマンドラインプログラムを構築する » mainからロジックを抽出する","id":"219","title":"mainからロジックを抽出する"},"22":{"body":"次にソースファイルを作り、 main.rs と呼んでください。Rustのファイルは常に .rs という拡張子で終わります。 ファイル名に2単語以上使っているなら、アンダースコアで区切ってください。例えば、 helloworld.rs ではなく、 hello_world.rs を使用してください。 さて、作ったばかりの main.rs ファイルを開き、リスト1-1のコードを入力してください。 ファイル名: main.rs fn main() { // 世界よ、こんにちは println!(\"Hello, world!\");\n} リスト1-1: Hello, world!と出力するプログラム ファイルを保存し、端末ウィンドウに戻ってください。LinuxかmacOSなら、以下のコマンドを打ってファイルをコンパイルし、 実行してください: $ rustc main.rs\n$ ./main\nHello, world! Windowsなら、./mainの代わりに.\\main.exeと打ちます: > rustc main.rs\n> .\\main.exe\nHello, world! OSに関わらず、Hello, world!という文字列が端末に出力されるはずです。この出力が見れないなら、 「トラブルシューティング」節に立ち戻って、助けを得る方法を参照してください。 Hello, world!が確かに出力されたら、おめでとうございます!正式にRustプログラムを書きました。 Rustプログラマになったのです!ようこそ!","breadcrumbs":"事始め » Rustプログラムを書いて走らせる","id":"22","title":"Rustプログラムを書いて走らせる"},"220":{"body":"ここまでminigrepは良さそうですね!では、テストを行え、 src/main.rs ファイルの責任が減らせるように、 src/main.rs ファイルを分割し、一部のコードを src/lib.rs ファイルに置きましょう。 main関数以外のコード全部を src/main.rs から src/lib.rs に移動しましょう: run関数定義 関係するuse文 Configの定義 Config::new関数定義 src/lib.rs の中身にはリスト12-13に示したようなシグニチャがあるはずです(関数の本体は簡潔性のために省略しました)。 リスト12-14で src/main.rs に変更を加えるまで、このコードはコンパイルできないことに注意してください。 ファイル名: src/lib.rs use std::error::Error;\nuse std::fs::File;\nuse std::io::prelude::*; pub struct Config { pub query: String, pub filename: String,\n} impl Config { pub fn new(args: &[String]) -> Result { // --snip-- }\n} pub fn run(config: Config) -> Result<(), Box> { // --snip--\n} リスト12-13: Configとrunを src/lib.rs に移動する ここでは、寛大にpubを使用しています: Configのフィールドとnewメソッドとrun関数です。 これでテスト可能な公開APIのあるライブラリクレートができました! さて、 src/lib.rs に移動したコードを src/main.rs のバイナリクレートのスコープに持っていく必要があります。 リスト12-14に示したようにですね。 ファイル名: src/main.rs extern crate minigrep; use std::env;\nuse std::process; use minigrep::Config; fn main() { // --snip-- if let Err(e) = minigrep::run(config) { // --snip-- }\n} リスト12-14: minigrepクレートを src/main.rs のスコープに持っていく ライブラリクレートをバイナリクレートに持っていくのに、extern crate minigrepを使用しています。 それからuse minigrep::Config行を追加してConfig型をスコープに持ってきて、 run関数にクレート名を接頭辞として付けます。これで全機能が連結され、動くはずです。 cargo runでプログラムを走らせて、すべてがうまくいっていることを確かめてください。 ふう!作業量が多かったですね。ですが、将来成功する準備はできています。 もう、エラー処理は遥かに楽になり、コードのモジュール化もできました。 ここから先の作業は、ほぼ src/lib.rs で完結するでしょう。 古いコードでは大変だけれども、新しいコードでは楽なことをして新発見のモジュール性を活用しましょう: テストを書くのです!","breadcrumbs":"入出力プロジェクト:コマンドラインプログラムを構築する » コードをライブラリクレートに分割する","id":"220","title":"コードをライブラリクレートに分割する"},"221":{"body":"今や、ロジックを src/lib.rs に抜き出し、引数集めとエラー処理を src/main.rs に残したので、 コードの核となる機能のテストを書くのが非常に容易になりました。いろんな引数で関数を直接呼び出し、 コマンドラインからバイナリを呼び出す必要なく戻り値を確認できます。ご自由にConfig::newやrun関数の機能のテストは、 ご自身でお書きください。 この節では、テスト駆動開発(TDD)過程を活用してminigrepプログラムに検索ロジックを追加します。 このソフトウェア開発テクニックは、以下の手順に従います: 失敗するテストを書き、走らせて想定通りの理由で失敗することを確かめる。 十分な量のコードを書くか変更して新しいテストを通過するようにする。 追加または変更したばかりのコードをリファクタリングし、テストが通り続けることを確認する。 手順1から繰り返す! この過程は、ソフトウェアを書く多くの方法のうちの一つに過ぎませんが、TDDによりコードデザインも駆動することができます。 テストを通過させるコードを書く前にテストを書くことで、過程を通して高いテストカバー率を保つ助けになります。 実際にクエリ文字列の検索を行う機能の実装をテスト駆動し、クエリに合致する行のリストを生成します。 この機能をsearchという関数に追加しましょう。","breadcrumbs":"入出力プロジェクト:コマンドラインプログラムを構築する » テスト駆動開発でライブラリの機能を開発する","id":"221","title":"テスト駆動開発でライブラリの機能を開発する"},"222":{"body":"もう必要ないので、プログラムの振る舞いを確認していたprintln!文を src/lib.rs と src/main.rs から削除しましょう。 それから src/lib.rs で、テスト関数のあるtestモジュールを追加します。第11章のようにですね。 このテスト関数がsearch関数に欲しい振る舞いを指定します: クエリとそれを検索するテキストを受け取り、 クエリを含む行だけをテキストから返します。リスト12-15にこのテストを示していますが、まだコンパイルは通りません。 ファイル名: src/lib.rs # fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {\n# vec![]\n# }\n#\n#[cfg(test)]\nmod test { use super::*; #[test] fn one_result() { let query = \"duct\"; // Rustは // 安全で速く生産性も高い。 // 3つ選んで。 let contents = \"\\\nRust:\nsafe, fast, productive.\nPick three.\"; assert_eq!( vec![\"safe, fast, productive.\"], search(query, contents) ); }\n} リスト12-15: こうだったらいいなというsearch関数の失敗するテストを作成する このテストは、\"duct.\"という文字列を検索します。検索対象の文字列は3行で、うち1行だけが\"duct.\"を含みます。 search関数から返る値が想定している行だけを含むことをアサーションします。 このテストを走らせ、失敗するところを観察することはできません。このテストはコンパイルもできないからです: まだsearch関数が存在していません!ゆえに今度は、空のベクタを常に返すsearch関数の定義を追加することで、 テストをコンパイルし走らせるだけのコードを追記します。リスト12-16に示したようにですね。そうすれば、 テストはコンパイルでき、失敗するはずです。なぜなら、空のベクタは、 \"safe, fast, productive.\"という行を含むベクタとは合致しないからです。 ファイル名: src/lib.rs pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { vec![]\n} リスト12-16: テストがコンパイルできるのに十分なだけsearch関数を定義する 明示的なライフタイムの'aがsearchのシグニチャで定義され、contents引数と戻り値で使用されていることに注目してください。 第10章からライフタイム仮引数は、どの実引数のライフタイムが戻り値のライフタイムに関連づけられているかを指定することを思い出してください。 この場合、返却されるベクタは、 (query引数ではなく)contents引数のスライスを参照する文字列スライスを含むべきと示唆しています。 言い換えると、コンパイラにsearch関数に返されるデータは、 search関数にcontents引数で渡されているデータと同期間生きることを教えています。 これは重要なことです!スライス に 参照されるデータは、参照が有効になるために有効である必要があるのです; コンパイラがcontentsではなくqueryの文字列スライスを生成すると想定してしまったら、 安全性チェックを間違って行うことになってしまいます。 ライフタイム注釈を忘れてこの関数をコンパイルしようとすると、こんなエラーが出ます: error[E0106]: missing lifetime specifier\n(エラー: ライフタイム指定子が欠けています) --> src/lib.rs:5:51 |\n5 | pub fn search(query: &str, contents: &str) -> Vec<&str> { | ^ expected lifetime\nparameter | = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `query` or `contents` (助言: この関数の戻り値は、借用された値を含んでいますが、シグニチャにはそれが、 `query`か`contents`から借用されたものであるかが示されていません) コンパイラには、二つの引数のどちらが必要なのか知る由がないので、教えてあげる必要があるのです。 contentsがテキストを全て含む引数で、合致するそのテキストの一部を返したいので、 contentsがライフタイム記法で戻り値に関連づくはずの引数であることをプログラマは知っています。 他のプログラミング言語では、シグニチャで引数と戻り値を関連づける必要はありません。これは奇妙に思えるかもしれませんが、 時間とともに楽になっていきます。この例を第10章、「ライフタイムで参照を有効化する」節と比較したくなるかもしれません。 さあ、テストを実行しましょう: $ cargo test Compiling minigrep v0.1.0 (file:///projects/minigrep)\n--warnings-- Finished dev [unoptimized + debuginfo] target(s) in 0.43 secs Running target/debug/deps/minigrep-abcabcabc running 1 test\ntest test::one_result ... FAILED failures: ---- test::one_result stdout ---- thread 'test::one_result' panicked at 'assertion failed: `(left ==\nright)`\nleft: `[\"safe, fast, productive.\"]`,\nright: `[]`)', src/lib.rs:48:8\nnote: Run with `RUST_BACKTRACE=1` for a backtrace. failures: test::one_result test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out error: test failed, to rerun pass '--lib' 素晴らしい。テストは全く想定通りに失敗しています。テストが通るようにしましょう!","breadcrumbs":"入出力プロジェクト:コマンドラインプログラムを構築する » 失敗するテストを記述する","id":"222","title":"失敗するテストを記述する"},"223":{"body":"空のベクタを常に返しているために、現状テストは失敗しています。それを修正し、searchを実装するには、 プログラムは以下の手順に従う必要があります: 中身を各行ごとに繰り返す。 行にクエリ文字列が含まれるか確認する。 するなら、それを返却する値のリストに追加する。 しないなら、何もしない。 一致する結果のリストを返す。 各行を繰り返す作業から、この手順に順に取り掛かりましょう。 linesメソッドで各行を繰り返す Rustには、文字列を行ごとに繰り返す役立つメソッドがあり、利便性のためにlinesと名付けられ、 リスト12-17のように動作します。まだ、これはコンパイルできないことに注意してください。 ファイル名: src/lib.rs pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { for line in contents.lines() { // 行に対して何かする // do something with line }\n} リスト12-17: contentsの各行を繰り返す linesメソッドはイテレータを返します。イテレータについて詳しくは、第13章で話しますが、 リスト3-5でこのようなイテレータの使用法は見かけたことを思い出してください。 そこでは、イテレータにforループを使用してコレクションの各要素に対して何らかのコードを走らせていました。 クエリを求めて各行を検索する 次に現在の行がクエリ文字列を含むか確認します。幸運なことに、 文字列にはこれを行ってくれるcontainsという役に立つメソッドがあります!search関数に、 containsメソッドの呼び出しを追加してください。リスト12-18のようにですね。 それでもまだコンパイルできないことに注意してください。 ファイル名: src/lib.rs pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { for line in contents.lines() { if line.contains(query) { // do something with line } }\n} リスト12-18: 行がqueryの文字列を含むか確認する機能を追加する 合致した行を保存する また、クエリ文字列を含む行を保存する方法が必要です。そのために、forループの前に可変なベクタを生成し、 pushメソッドを呼び出してlineをベクタに保存することができます。forループの後でベクタを返却します。 リスト12-19のようにですね。 ファイル名: src/lib.rs pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { let mut results = Vec::new(); for line in contents.lines() { if line.contains(query) { results.push(line); } } results\n} リスト12-19: 合致する行を保存したので、返すことができる これでsearch関数は、queryを含む行だけを返すはずであり、テストも通るはずです。 テストを実行しましょう: $ cargo test\n--snip--\nrunning 1 test\ntest test::one_result ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out テストが通り、動いていることがわかりました! ここで、テストが通過するよう保ったまま、同じ機能を保持しながら、検索関数の実装をリファクタリングする機会を考えることもできます。 検索関数のコードは悪すぎるわけではありませんが、イテレータの有用な機能の一部を活用していません。 この例には第13章で再度触れ、そこでは、イテレータをより深く探究し、さらに改善する方法に目を向けます。 run関数内でsearch関数を使用する search関数が動きテストできたので、run関数からsearchを呼び出す必要があります。config.queryの値と、 ファイルからrunが読み込むcontentsの値をsearch関数に渡す必要があります。 それからrunは、searchから返ってきた各行を出力するでしょう: ファイル名: src/lib.rs pub fn run(config: Config) -> Result<(), Box> { let mut f = File::open(config.filename)?; let mut contents = String::new(); f.read_to_string(&mut contents)?; for line in search(&config.query, &contents) { println!(\"{}\", line); } Ok(())\n} それでもforループでsearchから各行を返し、出力しています。 さて、プログラム全体が動くはずです!試してみましょう。まずはエミリー・ディキンソンの詩から、 ちょうど1行だけを返すはずの言葉から。\"frog\"です: $ cargo run frog poem.txt Compiling minigrep v0.1.0 (file:///projects/minigrep) Finished dev [unoptimized + debuginfo] target(s) in 0.38 secs Running `target/debug/minigrep frog poem.txt`\nHow public, like a frog かっこいい!今度は、複数行にマッチするであろう言葉を試しましょう。\"body\"とかね: $ cargo run body poem.txt Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs Running `target/debug/minigrep body poem.txt`\nI’m nobody! Who are you?\nAre you nobody, too?\nHow dreary to be somebody! そして最後に、詩のどこにも現れない単語を探したときに、何も出力がないことを確かめましょう。 \"monomorphization\"などね: $ cargo run monomorphization poem.txt Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs Running `target/debug/minigrep monomorphization poem.txt` 最高です!古典的なツールの独自のミニバージョンを構築し、アプリケーションを構造化する方法を多く学びました。 また、ファイル入出力、ライフタイム、テスト、コマンドライン引数の解析についても、少し学びました。 このプロジェクトをまとめ上げるために、環境変数を扱う方法と標準エラー出力に出力する方法を少しだけデモします。 これらはどちらも、コマンドラインプログラムを書く際に有用です。","breadcrumbs":"入出力プロジェクト:コマンドラインプログラムを構築する » テストを通過させるコードを書く","id":"223","title":"テストを通過させるコードを書く"},"224":{"body":"おまけの機能を追加してminigrepを改善します: 環境変数でユーザがオンにできる大文字小文字無視の検索用のオプションです。 この機能をコマンドラインオプションにして、適用したい度にユーザが入力しなければならないようにすることもできますが、 代わりに環境変数を使用します。そうすることでユーザは1回環境変数をセットすれば、そのターミナルセッションの間は、 大文字小文字無視の検索を行うことができるようになるわけです。","breadcrumbs":"入出力プロジェクト:コマンドラインプログラムを構築する » 環境変数を取り扱う","id":"224","title":"環境変数を取り扱う"},"225":{"body":"環境変数がオンの場合に呼び出すsearch_case_insensitive関数を新しく追加したいです。テスト駆動開発の過程に従い続けるので、 最初の手順は、今回も失敗するテストを書くことです。新しいsearch_case_insensitive関数用の新規テストを追加し、 古いテストをone_resultからcase_sensitiveに名前変更して、二つのテストの差異を明確化します。 リスト12-20に示したようにですね。 ファイル名: src/lib.rs #[cfg(test)]\nmod test { use super::*; #[test] fn case_sensitive() { let query = \"duct\";\n// Rust\n// 安全かつ高速で生産的\n// 三つを選んで\n// ガムテープ let contents = \"\\\nRust:\nsafe, fast, productive.\nPick three.\nDuct tape.\"; assert_eq!( vec![\"safe, fast, productive.\"], search(query, contents) ); } #[test] fn case_insensitive() { let query = \"rUsT\";\n// (最後の行のみ)\n// 私を信じて let contents = \"\\\nRust:\nsafe, fast, productive.\nPick three.\nTrust me.\"; assert_eq!( vec![\"Rust:\", \"Trust me.\"], search_case_insensitive(query, contents) ); }\n} リスト12-20: 追加しようとしている大文字小文字を区別しない関数用の失敗するテストを新しく追加する 古いテストのcontentsも変更していることに注意してください。大文字小文字を区別する検索を行う際に、 \"duct\"というクエリに合致しないはずの大文字Dを使用した\"Duct tape\"(ガムテープ)という新しい行を追加しました。 このように古いテストを変更することで、既に実装済みの大文字小文字を区別する検索機能を誤って壊してしまわないことを保証する助けになります。 このテストはもう通り、大文字小文字を区別しない検索に取り掛かっても通り続けるはずです。 大文字小文字を区別 しない 検索の新しいテストは、クエリに\"rUsT\"を使用しています。 追加直前のsearch_case_insensitive関数では、\"rUsT\"というクエリは、 両方ともクエリとは大文字小文字が異なるのに、大文字Rの\"Rust:\"を含む行と、 “Trust me.”という行にもマッチするはずです。これが失敗するテストであり、まだsearch_case_insensitive関数を定義していないので、 コンパイルは失敗するでしょう。リスト12-16のsearch関数で行ったのと同様に空のベクタを常に返すような仮実装を追加し、テストがコンパイルされるものの、失敗する様をご自由に確認してください。","breadcrumbs":"入出力プロジェクト:コマンドラインプログラムを構築する » 大文字小文字を区別しないsearch関数用に失敗するテストを書く","id":"225","title":"大文字小文字を区別しないsearch関数用に失敗するテストを書く"},"226":{"body":"search_case_insensitive関数は、リスト12-21に示しましたが、search関数とほぼ同じです。 唯一の違いは、queryと各lineを小文字化していることなので、入力引数の大文字小文字によらず、 行がクエリを含んでいるか確認する際には、同じになるわけです。 ファイル名: src/lib.rs pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { let query = query.to_lowercase(); let mut results = Vec::new(); for line in contents.lines() { if line.to_lowercase().contains(&query) { results.push(line); } } results\n} リスト12-21: 比較する前にクエリと行を小文字化するよう、search_case_insensitive関数を定義する まず、query文字列を小文字化し、同じ名前の覆い隠された変数に保存します。ユーザのクエリが\"rust\"や\"RUST\"、 \"Rust\"、\"rUsT\"などだったりしても、\"rust\"であり、大文字小文字を区別しないかのようにクエリを扱えるように、 to_lowercaseをクエリに対して呼び出すことは必須です。 queryは最早、文字列スライスではなくStringであることに注意してください。というのも、 to_lowercaseを呼び出すと、既存のデータを参照するというよりも、新しいデータを作成するからです。 例として、クエリは\"rUsT\"だとしましょう: その文字列スライスは、小文字のuやtを使えるように含んでいないので、 \"rust\"を含む新しいStringのメモリを確保しなければならないのです。今、containsメソッドに引数としてqueryを渡すと、 アンド記号を追加する必要があります。containsのシグニチャは、文字列スライスを取るよう定義されているからです。 次に、各lineがqueryを含むか確かめる前にto_lowercaseの呼び出しを追加し、全文字を小文字化しています。 今やlineとqueryを小文字に変換したので、クエリが大文字であろうと小文字であろうとマッチを検索するでしょう。 この実装がテストを通過するか確認しましょう: running 2 tests\ntest test::case_insensitive ... ok\ntest test::case_sensitive ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out 素晴らしい!どちらも通りました。では、run関数から新しいsearch_case_insensitive関数を呼び出しましょう。 1番目に大文字小文字の区別を切り替えられるよう、Config構造体に設定オプションを追加します。 まだどこでも、このフィールドの初期化をしていないので、追加するとコンパイルエラーが起きます: ファイル名: src/lib.rs pub struct Config { pub query: String, pub filename: String, pub case_sensitive: bool,\n} 論理値を持つcase_sensitiveフィールドを追加したことに注意してください。次に、run関数に、 case_sensitiveフィールドの値を確認し、search関数かsearch_case_insensitive関数を呼ぶかを決定するのに使ってもらう必要があります。 リスト12-22のようにですね。それでも、これはまだコンパイルできないことに注意してください。 ファイル名: src/lib.rs # use std::error::Error;\n# use std::fs::File;\n# use std::io::prelude::*;\n#\n# fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {\n# vec![]\n# }\n#\n# pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {\n# vec![]\n# }\n#\n# pub struct Config {\n# query: String,\n# filename: String,\n# case_sensitive: bool,\n# }\n#\npub fn run(config: Config) -> Result<(), Box> { let mut f = File::open(config.filename)?; let mut contents = String::new(); f.read_to_string(&mut contents)?; let results = if config.case_sensitive { search(&config.query, &contents) } else { search_case_insensitive(&config.query, &contents) }; for line in results { println!(\"{}\", line); } Ok(())\n} リスト12-22: config.case_sensitiveの値に基づいてsearchかsearch_case_insensitiveを呼び出す 最後に、環境変数を確認する必要があります。環境変数を扱う関数は、標準ライブラリのenvモジュールにあるので、 use std::env;行で src/lib.rs の冒頭でそのモジュールをスコープに持ってくる必要があります。そして、 envモジュールからvar関数を使用してCASE_INSENSITIVEという環境変数のチェックを行います。 リスト12-23のようにですね。 ファイル名: src/lib.rs use std::env;\n# struct Config {\n# query: String,\n# filename: String,\n# case_sensitive: bool,\n# } // --snip-- impl Config { pub fn new(args: &[String]) -> Result { if args.len() < 3 { return Err(\"not enough arguments\"); } let query = args[1].clone(); let filename = args[2].clone(); let case_sensitive = env::var(\"CASE_INSENSITIVE\").is_err(); Ok(Config { query, filename, case_sensitive }) }\n} リスト12-23: CASE_INSENSITIVEという環境変数のチェックを行う ここで、case_sensitiveという新しい変数を生成しています。その値をセットするために、 env::var関数を呼び出し、CASE_INSENSITIVE環境変数の名前を渡しています。env::var関数は、 環境変数がセットされていたら、環境変数の値を含むOk列挙子の成功値になるResultを返します。 環境変数がセットされていなければ、Err列挙子を返すでしょう。 Resultのis_errメソッドを使用して、エラーでありゆえに、セットされていないことを確認しています。 これは大文字小文字を区別する検索をす べき ことを意味します。CASE_INSENSITIVE環境変数が何かにセットされていれば、 is_errはfalseを返し、プログラムは大文字小文字を区別しない検索を実行するでしょう。環境変数の 値 はどうでもよく、 セットされているかどうかだけ気にするので、unwrapやexpectあるいは、他のここまで見かけたResultのメソッドではなく、 is_errをチェックしています。 case_sensitive変数の値をConfigインスタンスに渡しているので、リスト12-22で実装したように、 run関数はその値を読み取り、searchかsearch_case_insensitiveを呼び出すか決定できるのです。 試行してみましょう!まず、環境変数をセットせずにクエリはtoでプログラムを実行し、 この時は全て小文字で\"to\"という言葉を含むあらゆる行が合致するはずです。 $ cargo run to poem.txt Compiling minigrep v0.1.0 (file:///projects/minigrep) Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs Running `target/debug/minigrep to poem.txt`\nAre you nobody, too?\nHow dreary to be somebody! まだ機能しているようです!では、CASE_INSENSITIVEを1にしつつ、同じクエリのtoでプログラムを実行しましょう。 PowerShellを使用しているなら、1コマンドではなく、2コマンドで環境変数をセットし、プログラムを実行する必要があるでしょう: $ $env:CASE_INSENSITIVE=1\n$ cargo run to poem.txt 大文字も含む可能性のある\"to\"を含有する行が得られるはずです: $ CASE_INSENSITIVE=1 cargo run to poem.txt Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs Running `target/debug/minigrep to poem.txt`\nAre you nobody, too?\nHow dreary to be somebody!\nTo tell your name the livelong day\nTo an admiring bog! 素晴らしい、\"To\"を含む行も出てきましたね!minigrepプログラムはこれで、 環境変数によって制御できる大文字小文字を区別しない検索も行えるようになりました。もうコマンドライン引数か、 環境変数を使ってオプションを管理する方法も知りましたね。 引数 と 環境変数で同じ設定を行うことができるプログラムもあります。そのような場合、 プログラムはどちらが優先されるか決定します。自身の別の鍛錬として、コマンドライン引数か、 環境変数で大文字小文字の区別を制御できるようにしてみてください。 片方は大文字小文字を区別するようにセットされ、もう片方は区別しないようにセットしてプログラムが実行された時に、 コマンドライン引数と環境変数のどちらの優先度が高くなるかを決めてください。 std::envモジュールは、環境変数を扱うもっと多くの有用な機能を有しています: ドキュメンテーションを確認して、何が利用可能か確かめてください。","breadcrumbs":"入出力プロジェクト:コマンドラインプログラムを構築する » search_case_insensitive関数を実装する","id":"226","title":"search_case_insensitive関数を実装する"},"227":{"body":"現時点では、すべての出力をprintln!関数を使用して端末に書き込んでいます。多くの端末は、 2種類の出力を提供します: 普通の情報用の 標準出力 (stdout)とエラーメッセージ用の 標準エラー出力 (stderr)です。 この差異のおかげで、ユーザは、エラーメッセージを画面に表示しつつ、 プログラムの成功した出力をファイルにリダイレクトすることを選択できます。 println!関数は、標準出力に出力する能力しかないので、標準エラーに出力するには他のものを使用しなければなりません。","breadcrumbs":"入出力プロジェクト:コマンドラインプログラムを構築する » 標準出力ではなく標準エラーにエラーメッセージを書き込む","id":"227","title":"標準出力ではなく標準エラーにエラーメッセージを書き込む"},"228":{"body":"まず、minigrepに出力される中身が、代わりに標準エラーに書き込みたいいかなるエラーメッセージも含め、 どのように標準出力に書き込まれているかを観察しましょう。意図的にエラーを起こしつつ、 ファイルに標準出力ストリームをリダイレクトすることでそうします。標準エラーストリームはリダイレクトしないので、 標準エラーに送られる内容は、すべて画面に表示され続けます。 コマンドラインプログラムは、エラーメッセージを標準エラー出力に送信していると期待されているので、 標準出力ストリームをファイルにリダイレクトしても、画面にエラーメッセージが見られます。 我々のプログラムは、現状、いい振る舞いをしていません: 代わりにファイルにエラーメッセージ出力を保存するところを、 目撃するところです! この動作をデモする方法は、>と標準出力ストリームをリダイレクトする先のファイル名、 output.txt でプログラムを走らせることによります。 引数は何も渡さず、そうするとエラーが起きるはずです: $ cargo run > output.txt >記法により、標準出力の中身を画面の代わりに output.txt に書き込むようシェルは指示されます。 画面に出力されると期待していたエラーメッセージは見られないので、ファイルに入っているということでしょう。 以下が output.txt が含んでいる内容です: Problem parsing arguments: not enough arguments そうです。エラーメッセージは標準出力に出力されているのです。このようなエラーメッセージは標準エラーに出力され、 成功した状態のデータのみがファイルに残ると遥かに有用です。それを変更します。","breadcrumbs":"入出力プロジェクト:コマンドラインプログラムを構築する » エラーが書き込まれる場所を確認する","id":"228","title":"エラーが書き込まれる場所を確認する"},"229":{"body":"リスト12-24のコードを使用して、エラーメッセージの出力の仕方を変更します。この章の前で行ったリファクタリングのため、 エラーメッセージを出力するコードはすべて1関数、mainにあります。標準ライブラリは、 標準エラーストリームに出力するeprintln!マクロを提供しているので、 println!を呼び出してエラーを出力していた2箇所を代わりにeprintln!を使うように変更しましょう。 ファイル名: src/main.rs fn main() { let args: Vec = env::args().collect(); let config = Config::new(&args).unwrap_or_else(|err| { eprintln!(\"Problem parsing arguments: {}\", err); process::exit(1); }); if let Err(e) = minigrep::run(config) { eprintln!(\"Application error: {}\", e); process::exit(1); }\n} リスト12-24: eprintln!を使って標準出力ではなく、標準エラーにエラーメッセージを書き込む println!をeprintln!に変えてから、再度同じようにプログラムを実行しましょう。 引数なしかつ、標準出力を>でリダイレクトしてね: $ cargo run > output.txt\nProblem parsing arguments: not enough arguments これで、エラーは画面に見えつつ、 output.txt は何も含まなくなり、これはコマンドラインプログラムに期待する動作です。 再度、標準出力をファイルにリダイレクトしてエラーは起こさない引数でプログラムを走らせましょう。以下のようにですね: $ cargo run to poem.txt > output.txt ターミナルには出力は見られず、 output.txt に結果が含まれます: ファイル名: output.txt Are you nobody, too?\nHow dreary to be somebody! これは、もう成功した出力には標準出力を、エラー出力には標準エラーを適切に使用していることをデモしています。","breadcrumbs":"入出力プロジェクト:コマンドラインプログラムを構築する » エラーを標準エラーに出力する","id":"229","title":"エラーを標準エラーに出力する"},"23":{"body":"Hello, world!プログラムでちょうど何が起こったのか詳しく確認しましょう。 こちらがパズルの最初のピースです: fn main() { } これらの行でRustで関数を定義しています。main関数は特別です: 常に全ての実行可能なRustプログラムで走る最初のコードになります。 1行目は、引数がなく、何も返さないmainという関数を宣言しています。引数があるなら、かっこ(())の内部に入ります。 また、関数の本体が波括弧({})に包まれていることにも注目してください。Rustでは、全ての関数本体の周りにこれらが必要になります。 スペースを1つあけて、開き波括弧を関数宣言と同じ行に配置するのがいいスタイルです。 これを執筆している時点では、rustfmtと呼ばれる自動整形ツールは開発中です。複数のRustプロジェクトに渡って、 標準的なスタイルに固執したいなら、rustfmtは特定のスタイルにコードを整形してくれます。Rustチームは、 最終的にrustcのように標準的なRustの配布にこのツールを含むことを計画しています。従って、この本を読んだ時期によっては、 既にコンピュータにインストールされている可能性もあります!詳細は、オンラインのドキュメンテーションを確認してください。 main関数内には、こんなコードがあります: println!(\"Hello, world!\"); この行が、この小さなプログラムの全作業をしています: テキストを画面に出力するのです。 ここで気付くべき重要な詳細が4つあります。まず、Rustのスタイルは、タブではなく、4スペースでインデントするということです。 2番目にprintln!はRustのマクロを呼び出すということです。代わりに関数を呼んでいたら、 println(!なし)と入力されているでしょう。Rustのマクロについて詳しくは、付録Dで議論します。 とりあえず、!を使用すると、普通の関数ではなくマクロを呼んでいるのだということを知っておくだけでいいでしょう。 3番目に、\"Hello, world!\"文字列が見えます。この文字列を引数としてprintln!に渡し、 この文字列が画面に表示されているのです。 4番目にこの行をセミコロン(;)で終え、この式が終わり、次の式の準備ができていると示唆していることです。 Rustコードのほとんどの行は、セミコロンで終わります。","breadcrumbs":"事始め » Rustプログラムの解剖","id":"23","title":"Rustプログラムの解剖"},"230":{"body":"この章では、ここまでに学んできた主要な概念の一部を念押しし、Rustで入出力処理を行う方法を講義しました。 コマンドライン引数、ファイル、環境変数、そしてエラー出力にeprintln!マクロを使用することで、 もう、コマンドラインアプリケーションを書く準備ができています。以前の章の概念を使用することで、 コードはうまく体系化され、適切なデータ構造に効率的にデータを保存し、エラーをうまく扱い、 よくテストされるでしょう。 次は、関数型言語に影響されたRust機能を一部探究します: クロージャとイテレータです。","breadcrumbs":"入出力プロジェクト:コマンドラインプログラムを構築する » まとめ","id":"230","title":"まとめ"},"231":{"body":"Rustの設計は、多くの既存の言語やテクニックにインスピレーションを得ていて、 その一つの大きな影響が 関数型プログラミング です。関数型でのプログラミングには、しばしば、 引数で渡したり、関数から関数を返したり、関数を後ほど使用するために変数に代入することで関数を値として使用することが含まれます。 この章では、関数型プログラミングがどんなものであったり、なかったりするかという問題については議論しませんが、 代わりに関数型とよく言及される多くの言語の機能に似たRustの機能の一部について議論しましょう。 具体的には、以下を講義します: クロージャ 、変数に保存できる関数に似た文法要素 イテレータ 、一連の要素を処理する方法 これら2つの機能を使用して第12章の入出力プロジェクトを改善する方法 これら2つの機能のパフォーマンス(ネタバレ: 思ったよりも速いです) パターンマッチングやenumなど、他のRustの機能も関数型に影響されていますが、他の章で講義してきました。 クロージャとイテレータをマスターすることは、慣用的で速いRustコードを書くことの重要な部分なので、 この章を丸ごと捧げます。","breadcrumbs":"関数型言語の機能: イテレータとクロージャ","id":"231","title":"関数型言語の機能: イテレータとクロージャ"},"232":{"body":"Rustのクロージャは、変数に保存したり、引数として他の関数に渡すことのできる匿名関数です。 ある場所でクロージャを生成し、それから別の文脈でクロージャを呼び出して評価することができます。 関数と異なり、呼び出されたスコープの値をクロージャは、キャプチャすることができます。 これらのクロージャの機能がコードの再利用や、動作のカスタマイズを行わせてくれる方法を模擬しましょう。","breadcrumbs":"関数型言語の機能:イテレータとクロージャ » クロージャ: 環境をキャプチャできる匿名関数","id":"232","title":"クロージャ: 環境をキャプチャできる匿名関数"},"233":{"body":"クロージャを保存して後々使用できるようにするのが有用な場面の例に取り掛かりましょう。その過程で、 クロージャの記法、型推論、トレイトについて語ります。 以下のような架空の場面を考えてください: カスタマイズされたエクササイズのトレーニングプランを生成するアプリを作るスタートアップで働くことになりました。 バックエンドはRustで記述され、トレーニングプランを生成するアルゴリズムは、アプリユーザの年齢や、 BMI、運動の好み、最近のトレーニング、指定された強弱値などの多くの要因を考慮します。 実際に使用されるアルゴリズムは、この例では重要ではありません; 重要なのは、この計算が数秒要することです。 必要なときだけこのアルゴリズムを呼び出し、1回だけ呼び出したいので、必要以上にユーザを待たせないことになります。 リスト13-1に示したsimulated_expensive_calculation関数でこの仮定のアルゴリズムを呼び出すことをシミュレートし、 この関数はcalculating slowlyと出力し、2秒待ってから、渡した数値をなんでも返します。 ファイル名: src/main.rs use std::thread;\nuse std::time::Duration; fn simulated_expensive_calculation(intensity: u32) -> u32 { // ゆっくり計算します println!(\"calculating slowly...\"); thread::sleep(Duration::from_secs(2)); intensity\n} リスト13-1: 実行に約2秒かかる架空の計算の代役を務める関数 次は、この例で重要なトレーニングアプリの部分を含むmain関数です。この関数は、 ユーザがトレーニングプランを要求した時にアプリが呼び出すコードを表します。 アプリのフロントエンドと相互作用する部分は、クロージャの使用と関係ないので、プログラムへの入力を表す値をハードコードし、 その出力を表示します。 必要な入力は以下の通りです: ユーザの強弱値、これはユーザがトレーニングを要求して、低強度のトレーニングか、 高強度のトレーニングがしたいかを示したときに指定されます。 乱数、これはトレーニングプランにバリエーションを起こします。 出力は、推奨されるトレーニングプランになります。リスト13-2は使用するmain関数を示しています。 ファイル名: src/main.rs fn main() { let simulated_user_specified_value = 10; let simulated_random_number = 7; generate_workout( simulated_user_specified_value, simulated_random_number );\n}\n# fn generate_workout(intensity: u32, random_number: u32) {} リスト13-2: ユーザ入力や乱数生成をシミュレートするハードコードされた値があるmain関数 簡潔性のために、変数simulated_user_specified_valueは10、変数simulated_random_numberは7とハードコードしました; 実際のプログラムにおいては、強弱値はアプリのフロントエンドから取得し、乱数の生成には、第2章の数当てゲームの例のように、randクレートを使用するでしょう。 main関数は、シミュレートされた入力値とともにgenerate_workout関数を呼び出します。 今や文脈ができたので、アルゴリズムに取り掛かりましょう。リスト13-3のgenerate_workout関数は、 この例で最も気にかかるアプリのビジネスロジックを含んでいます。この例での残りの変更は、 この関数に対して行われるでしょう: ファイル名: src/main.rs # use std::thread;\n# use std::time::Duration;\n#\n# fn simulated_expensive_calculation(num: u32) -> u32 {\n# println!(\"calculating slowly...\");\n# thread::sleep(Duration::from_secs(2));\n# num\n# }\n#\nfn generate_workout(intensity: u32, random_number: u32) { if intensity < 25 { println!( // 今日は{}回腕立て伏せをしてください! \"Today, do {} pushups!\", simulated_expensive_calculation(intensity) ); println!( // 次に、{}回腹筋をしてください! \"Next, do {} situps!\", simulated_expensive_calculation(intensity) ); } else { if random_number == 3 { // 今日は休憩してください!水分補給を忘れずに! println!(\"Take a break today! Remember to stay hydrated!\"); } else { println!( // 今日は、{}分間走ってください! \"Today, run for {} minutes!\", simulated_expensive_calculation(intensity) ); } }\n} リスト13-3: 入力に基づいてトレーニングプランを出力するビジネスロジックと、 simulated_expensive_calculation関数の呼び出し リスト13-3のコードには、遅い計算を行う関数への呼び出しが複数あります。最初のifブロックが、 simulated_expensive_calculationを2回呼び出し、外側のelse内のifは全く呼び出さず、 2番目のelseケースの内側にあるコードは1回呼び出しています。 generate_workout関数の期待される振る舞いは、まずユーザが低強度のトレーニング(25より小さい数値で表される)か、 高強度のトレーニング(25以上の数値)を欲しているか確認することです。 低強度のトレーニングプランは、シミュレーションしている複雑なアルゴリズムに基づいて、 多くの腕立て伏せや腹筋運動を推奨してきます。 ユーザが高強度のトレーニングを欲していれば、追加のロジックがあります: アプリが生成した乱数がたまたま3なら、 アプリは休憩と水分補給を勧めます。そうでなければ、ユーザは複雑なアルゴリズムに基づいて数分間のランニングをします。 このコードは現在、ビジネスのほしいままに動くでしょうが、データサイエンスチームが、 simulated_expensive_calculation関数を呼び出す方法に何らかの変更を加える必要があると決定したとしましょう。 そのような変更が起きた時に更新を簡略化するため、simulated_expensive_calculation関数を1回だけ呼び出すように、 このコードをリファクタリングしたいです。また、その過程でその関数への呼び出しを増やすことなく無駄に2回、 この関数を現時点で呼んでいるところを切り捨てたくもあります。要するに、結果が必要なければ関数を呼び出したくなく、 それでも1回だけ呼び出したいのです。 関数でリファクタリング 多くの方法でトレーニングプログラムを再構築することもできます。 1番目にsimulated_expensive_calculation関数への重複した呼び出しを変数に抽出しようとしましょう。リスト13-4に示したように。 ファイル名: src/main.rs # use std::thread;\n# use std::time::Duration;\n#\n# fn simulated_expensive_calculation(num: u32) -> u32 {\n# println!(\"calculating slowly...\");\n# thread::sleep(Duration::from_secs(2));\n# num\n# }\n#\nfn generate_workout(intensity: u32, random_number: u32) { let expensive_result = simulated_expensive_calculation(intensity); if intensity < 25 { println!( \"Today, do {} pushups!\", expensive_result ); println!( \"Next, do {} situps!\", expensive_result ); } else { if random_number == 3 { println!(\"Take a break today! Remember to stay hydrated!\"); } else { println!( \"Today, run for {} minutes!\", expensive_result ); } }\n} リスト13-4: 複数のsimulated_expensive_calculationの呼び出しを1箇所に抽出し、 結果をexpensive_result変数に保存する この変更によりsimulated_expensive_calculationの呼び出しが単一化され、 最初のifブロックが無駄に関数を2回呼んでいた問題を解決します。不幸なことに、これでは、 あらゆる場合にこの関数を呼び出し、その結果を待つことになり、結果値を全く使用しない内側のifブロックでもそうしてしまいます。 プログラムの1箇所でコードを定義したいですが、結果が本当に必要なところでだけコードを 実行 します。 これは、クロージャのユースケースです! クロージャでリファクタリングして、コードを保存する ifブロックの前にいつもsimulated_expensive_calculation関数を呼び出す代わりに、 クロージャを定義し、関数呼び出しの結果を保存するのではなく、その クロージャ を変数に保存できます。リスト13-5のようにですね。 simulated_expensive_calculationの本体全体を実際に、ここで導入しているクロージャ内に移すことができます。 ファイル名: src/main.rs # use std::thread;\n# use std::time::Duration;\n#\nlet expensive_closure = |num| { println!(\"calculating slowly...\"); thread::sleep(Duration::from_secs(2)); num\n};\n# expensive_closure(5); リスト13-5: クロージャを定義し、expensive_closure変数に保存する クロージャ定義が=に続き、変数expensive_closureに代入されています。クロージャを定義するには、 1組の縦棒から始め、その内部にクロージャの仮引数を指定します; この記法は、SmalltalkやRubyのクロージャ定義と類似していることから、 選択されました。このクロージャには、numという引数が1つあります: 2つ以上引数があるなら、 |param1, param2|のように、カンマで区切ります。 引数の後に、クロージャの本体を保持する波括弧を配置します(これはクロージャ本体が式一つなら省略可能です)。 波括弧の後、クロージャのお尻には、セミコロンが必要で、let文を完成させます。クロージャ本体の最後の行から返る値(num)が、 呼び出された時にクロージャから返る値になります。その行がセミコロンで終わっていないからです; ちょうど関数の本体みたいですね。 このlet文は、expensive_closureが、匿名関数を呼び出した 結果の値 ではなく、 匿名関数の 定義 を含むことを意味することに注意してください。コードを定義して、 1箇所で呼び出し、そのコードを保存し、後々、それを呼び出したいがためにクロージャを使用していることを思い出してください; 呼び出したいコードは、現在、expensive_closureに保存されています。 クロージャが定義されたので、ifブロックのコードを変更して、そのコードを実行するクロージャを呼び出し、結果値を得ることができます。 クロージャは、関数のように呼び出せます: クロージャ定義を含む変数名を指定し、使用したい引数値を含むかっこを続けます。 リスト13-6に示したようにですね。 ファイル名: src/main.rs # use std::thread;\n# use std::time::Duration;\n#\nfn generate_workout(intensity: u32, random_number: u32) { let expensive_closure = |num| { println!(\"calculating slowly...\"); thread::sleep(Duration::from_secs(2)); num }; if intensity < 25 { println!( \"Today, do {} pushups!\", expensive_closure(intensity) ); println!( \"Next, do {} situps!\", expensive_closure(intensity) ); } else { if random_number == 3 { println!(\"Take a break today! Remember to stay hydrated!\"); } else { println!( \"Today, run for {} minutes!\", expensive_closure(intensity) ); } }\n} リスト13-6: 定義したexpensive_closureを呼び出す 今では、重い計算はたった1箇所でのみ呼び出され、その結果が必要なコードを実行するだけになりました。 ところが、リスト13-3の問題の一つを再浮上させてしまいました: それでも、最初のifブロックでクロージャを2回呼んでいて、 そうすると、重いコードを2回呼び出し、必要な分の2倍ユーザを待たせてしまいます。そのifブロックのみに属する変数を生成して、 クロージャの呼び出し結果を保持するそのifブロックに固有の変数を生成することでこの問題を解消することもできますが、 クロージャは他の解決法も用意してくれます。その解決策については、もう少し先で語りましょう。でもまずは、 クロージャ定義に型注釈がない理由とクロージャに関わるトレイトについて話しましょう。","breadcrumbs":"関数型言語の機能:イテレータとクロージャ » クロージャで動作の抽象化を行う","id":"233","title":"クロージャで動作の抽象化を行う"},"234":{"body":"クロージャでは、fn関数のように引数の型や戻り値の型を注釈する必要はありません。関数では、 型注釈は必要です。ユーザに露出する明示的なインターフェイスの一部だからです。このインターフェイスを堅実に定義することは、 関数が使用したり、返したりする値の型についてみんなが合意していることを保証するために重要なのです。 しかし、クロージャはこのような露出するインターフェイスには使用されません: 変数に保存され、 名前付けしたり、ライブラリの使用者に晒されることなく、使用されます。 クロージャは通常短く、あらゆる任意の筋書きではなく、狭い文脈でのみ関係します。 このような限定された文脈内では、コンパイラは、多くの変数の型を推論できるのと似たように、 引数や戻り値の型を頼もしく推論することができます。 このような小さく匿名の関数で型をプログラマに注釈させることは煩わしいし、コンパイラがすでに利用可能な情報と大きく被っています。 本当に必要な以上に冗長になることと引き換えに、明示性と明瞭性を向上させたいなら、変数に型注釈を加えることもできます; リスト13-5で定義したクロージャに型を注釈するなら、リスト13-7に示した定義のようになるでしょう。 ファイル名: src/main.rs # use std::thread;\n# use std::time::Duration;\n#\nlet expensive_closure = |num: u32| -> u32 { println!(\"calculating slowly...\"); thread::sleep(Duration::from_secs(2)); num\n}; リスト13-7: クロージャの引数と戻り値の省略可能な型注釈を追加する 型注釈を付け加えると、クロージャの記法は、関数の記法により酷似して見えます。以下が、引数に1を加える関数の定義と、 同じ振る舞いをするクロージャの定義の記法を縦に比べたものです。 空白を追加して、関連のある部分を並べています。これにより、縦棒の使用と省略可能な記法の量を除いて、 クロージャ記法が関数記法に似ているところを説明しています。 fn add_one_v1 (x: u32) -> u32 { x + 1 }\nlet add_one_v2 = |x: u32| -> u32 { x + 1 };\nlet add_one_v3 = |x| { x + 1 };\nlet add_one_v4 = |x| x + 1 ; 1行目が関数定義を示し、2行目がフルに注釈したクロージャ定義を示しています。 3行目は、クロージャ定義から型注釈を取り除き、4行目は、かっこを取り除いていて、 かっこはクロージャの本体がただ1つの式からなるので、省略可能です。これらは全て、 呼び出された時に同じ振る舞いになる合法な定義です。 クロージャ定義には、引数それぞれと戻り値に対して推論される具体的な型が一つあります。例えば、 リスト13-8に引数として受け取った値を返すだけの短いクロージャの定義を示しました。 このクロージャは、この例での目的以外には有用ではありません。この定義には、 何も型注釈を加えていないことに注意してください: それから1回目にStringを引数に、 2回目にu32を引数に使用してこのクロージャを2回呼び出そうとしたら、エラーになります。 ファイル名: src/main.rs let example_closure = |x| x; let s = example_closure(String::from(\"hello\"));\nlet n = example_closure(5); リスト13-8: 2つの異なる型で型が推論されるクロージャの呼び出しを試みる コンパイラは、次のエラーを返します: error[E0308]: mismatched types --> src/main.rs | | let n = example_closure(5); | ^ expected struct `std::string::String`, found integral variable | = note: expected type `std::string::String` found type `{integer}` String値でexample_closureを呼び出した最初の時点で、コンパイラはxとクロージャの戻り値の型をStringと推論します。 そして、その型がexample_closureのクロージャに閉じ込められ、同じクロージャを異なる型で使用しようとすると、 型エラーが出るのです。","breadcrumbs":"関数型言語の機能:イテレータとクロージャ » クロージャの型推論と注釈","id":"234","title":"クロージャの型推論と注釈"},"235":{"body":"トレーニング生成アプリに戻りましょう。リスト13-6において、まだコードは必要以上の回数、重い計算のクロージャを呼んでいました。 この問題を解決する一つの選択肢は、重いクロージャの結果を再利用できるように変数に保存し、クロージャを再度呼ぶ代わりに、 結果が必要になる箇所それぞれでその変数を使用することです。しかしながら、この方法は同じコードを大量に繰り返す可能性があります。 運のいいことに、別の解決策もあります。クロージャやクロージャの呼び出し結果の値を保持する構造体を作れるのです。 結果の値が必要な場合のみにその構造体はクロージャを実行し、その結果の値をキャッシュするので、残りのコードは、 結果を保存し、再利用する責任を負わなくて済むのです。このパターンは、 メモ化 (memoization)または、 遅延評価 (lazy evaluation)として知っているかもしれません。 クロージャを保持する構造体を作成するために、クロージャの型を指定する必要があります。 構造体定義は、各フィールドの型を把握しておく必要がありますからね。各クロージャインスタンスには、 独自の匿名の型があります: つまり、たとえ2つのクロージャが全く同じシグニチャでも、その型はそれでも違うものと考えられるということです。 クロージャを使用する構造体、enum、関数引数を定義するには、第10章で議論したように、 ジェネリクスとトレイト境界を使用します。 Fnトレイトは、標準ライブラリで用意されています。全てのクロージャは、以下のいずれかのトレイトを実装しています: Fn、FnMutまたは、FnOnceです。「クロージャで環境をキャプチャする」節で、これらのトレイト間の差異を議論します; この例では、Fnトレイトを使えます。 Fnトレイト境界にいくつかの型を追加することで、このトレイト境界に合致するクロージャが持つべき引数と戻り値の型を示します。 今回のクロージャはu32型の引数を一つ取り、u32を返すので、指定するトレイト境界はFn(u32) -> u32になります。 リスト13-9は、クロージャとオプションの結果値を保持するCacher構造体の定義を示しています。 ファイル名: src/main.rs struct Cacher where T: Fn(u32) -> u32\n{ calculation: T, value: Option,\n} リスト13-9: クロージャをcalculationに、オプションの結果値をvalueに保持するCacher構造体を定義する Cacher構造体は、ジェネリックな型Tのcalculationフィールドを持ちます。Tのトレイト境界は、 Fnトレイトを使うことでクロージャであると指定しています。calculationフィールドに保存したいクロージャは全て、 1つのu32引数(Fnの後の括弧内で指定されている)を取り、u32(->の後に指定されている)を返さなければなりません。 注釈: 関数も3つのFnトレイト全部を実装します。もし環境から値をキャプチャする必要がなければ、 Fnトレイトを実装する何かが必要になるクロージャではなく、関数を使用できます。 valueフィールドの型は、Optionです。クロージャを実行する前に、valueはNoneになるでしょう。 Cacherを使用するコードがクロージャの 結果 を求めてきたら、その時点でCacherはクロージャを実行し、 その結果をvalueフィールドのSome列挙子に保存します。それから、コードが再度クロージャの結果を求めたら、 クロージャを再実行するのではなく、CacherはSome列挙子に保持された結果を返すでしょう。 たった今解説したvalueフィールド周りのロジックは、リスト13-10で定義されています。 ファイル名: src/main.rs # struct Cacher\n# where T: Fn(u32) -> u32\n# {\n# calculation: T,\n# value: Option,\n# }\n#\nimpl Cacher where T: Fn(u32) -> u32\n{ fn new(calculation: T) -> Cacher { Cacher { calculation, value: None, } } fn value(&mut self, arg: u32) -> u32 { match self.value { Some(v) => v, None => { let v = (self.calculation)(arg); self.value = Some(v); v }, } }\n} リスト13-10: Cacherのキャッシュ機構 呼び出し元のコードにこれらのフィールドの値を直接変えてもらうのではなく、Cacherに構造体のフィールドの値を管理してほしいので、 これらのフィールドは非公開になっています。 Cacher::new関数はジェネリックな引数のTを取り、Cacher構造体と同じトレイト境界を持つよう定義しました。 それからcalculationフィールドに指定されたクロージャと、 valueフィールドにNone値を保持するCacherインスタンスをCacher::newは返します。 まだクロージャを実行していないからですね。 呼び出し元のコードがクロージャの評価結果を必要としたら、クロージャを直接呼ぶ代わりに、valueメソッドを呼びます。 このメソッドは、結果の値がself.valueのSomeに既にあるかどうか確認します; そうなら、 クロージャを再度実行することなくSome内の値を返します。 self.valueがNoneなら、コードはself.calculationに保存されたクロージャを呼び出し、 結果を将来使えるようにself.valueに保存し、その値を返しもします。 リスト13-11は、リスト13-6の関数generate_workoutでこのCacher構造体を使用する方法を示しています。 ファイル名: src/main.rs # use std::thread;\n# use std::time::Duration;\n#\n# struct Cacher\n# where T: Fn(u32) -> u32\n# {\n# calculation: T,\n# value: Option,\n# }\n#\n# impl Cacher\n# where T: Fn(u32) -> u32\n# {\n# fn new(calculation: T) -> Cacher {\n# Cacher {\n# calculation,\n# value: None,\n# }\n# }\n#\n# fn value(&mut self, arg: u32) -> u32 {\n# match self.value {\n# Some(v) => v,\n# None => {\n# let v = (self.calculation)(arg);\n# self.value = Some(v);\n# v\n# },\n# }\n# }\n# }\n#\nfn generate_workout(intensity: u32, random_number: u32) { let mut expensive_result = Cacher::new(|num| { println!(\"calculating slowly...\"); thread::sleep(Duration::from_secs(2)); num }); if intensity < 25 { println!( \"Today, do {} pushups!\", expensive_result.value(intensity) ); println!( \"Next, do {} situps!\", expensive_result.value(intensity) ); } else { if random_number == 3 { println!(\"Take a break today! Remember to stay hydrated!\"); } else { println!( \"Today, run for {} minutes!\", expensive_result.value(intensity) ); } }\n} リスト13-11: generate_workout関数内でCacherを使用し、キャッシュ機構を抽象化する クロージャを変数に直接保存する代わりに、クロージャを保持するCacherの新規インスタンスを保存しています。 そして、結果が必要な場所それぞれで、そのCacherインスタンスに対してvalueメソッドを呼び出しています。 必要なだけvalueメソッドを呼び出したり、全く呼び出さないこともでき、重い計算は最大でも1回しか走りません。 リスト13-2のmain関数とともにこのプログラムを走らせてみてください。 simulated_user_specified_valueとsimulated_random_number変数の値を変えて、 いろんなifやelseブロックの場合全てで、calculating slowlyは1回だけ、必要な時にのみ出現することを実証してください。 必要以上に重い計算を呼び出さないことを保証するのに必要なロジックの面倒をCacherは見るので、 generate_workoutはビジネスロジックに集中できるのです。","breadcrumbs":"関数型言語の機能:イテレータとクロージャ » ジェネリック引数とFnトレイトを使用してクロージャを保存する","id":"235","title":"ジェネリック引数とFnトレイトを使用してクロージャを保存する"},"236":{"body":"値をキャッシュすることは、コードの他の部分でも異なるクロージャで行いたくなる可能性のある一般的に有用な振る舞いです。 しかし、現在のCacherの実装には、他の文脈で再利用することを困難にしてしまう問題が2つあります。 1番目の問題は、Cacherインスタンスが、常にvalueメソッドの引数argに対して同じ値になると想定していることです。 言い換えると、Cacherのこのテストは、失敗するでしょう: #[test]\nfn call_with_different_values() { let mut c = Cacher::new(|a| a); let v1 = c.value(1); let v2 = c.value(2); assert_eq!(v2, 2);\n} このテストは、渡された値を返すクロージャを伴うCacherインスタンスを新しく生成しています。 このCacherインスタンスに対して1というarg値で呼び出し、それから2というarg値で呼び出し、 2というarg値のvalue呼び出しは2を返すべきと期待しています。 このテストをリスト13-9とリスト13-10のCacher実装で動かすと、assert_eqからこんなメッセージが出て、 テストは失敗します: thread 'call_with_different_values' panicked at 'assertion failed: `(left == right)` left: `1`, right: `2`', src/main.rs 問題は、初めてc.valueを1で呼び出した時に、Cacherインスタンスはself.valueにSome(1)を保存したことです。 その後valueメソッドに何を渡しても、常に1を返すわけです。 単独の値ではなく、ハッシュマップを保持するようにCacherを改変してみてください。ハッシュマップのキーは、 渡されるarg値になり、ハッシュマップの値は、そのキーでクロージャを呼び出した結果になるでしょう。 self.valueが直接SomeかNone値であることを調べる代わりに、value関数はハッシュマップのargを調べ、 存在するならその値を返します。存在しないなら、Cacherはクロージャを呼び出し、 arg値に紐づけてハッシュマップに結果の値を保存します。 現在のCacher実装の2番目の問題は、引数の型にu32を一つ取り、u32を返すクロージャしか受け付けないことです。 例えば、文字列スライスを取り、usizeを返すクロージャの結果をキャッシュしたくなるかもしれません。 この問題を修正するには、Cacher機能の柔軟性を向上させるためによりジェネリックな引数を導入してみてください。","breadcrumbs":"関数型言語の機能:イテレータとクロージャ » Cacher実装の限界","id":"236","title":"Cacher実装の限界"},"237":{"body":"トレーニング生成の例においては、クロージャをインラインの匿名関数として使っただけでした。しかし、 クロージャには、関数にはない追加の能力があります: 環境をキャプチャし、 自分が定義されたスコープの変数にアクセスできるのです。 リスト13-12は、equal_to_x変数に保持されたクロージャを囲む環境からx変数を使用するクロージャの例です。 ファイル名: src/main.rs fn main() { let x = 4; let equal_to_x = |z| z == x; let y = 4; assert!(equal_to_x(y));\n} リスト13-12: 内包するスコープの変数を参照するクロージャの例 ここで、xはequal_to_xの引数でもないのに、 equal_to_xが定義されているのと同じスコープで定義されているx変数をequal_to_xクロージャは使用できています。 同じことを関数では行うことができません; 以下の例で試したら、コードはコンパイルできません: ファイル名: src/main.rs fn main() { let x = 4; fn equal_to_x(z: i32) -> bool { z == x } let y = 4; assert!(equal_to_x(y));\n} エラーが出ます: error[E0434]: can't capture dynamic environment in a fn item; use the || { ...\n} closure form instead\n(エラー: fn要素では動的な環境をキャプチャできません; 代わりに|| { ... }のクロージャ形式を\n使用してください) --> src/main.rs |\n4 | fn equal_to_x(z: i32) -> bool { z == x } | ^ コンパイラは、この形式はクロージャでのみ動作することさえも思い出させてくれています! クロージャが環境から値をキャプチャすると、メモリを使用してクロージャ本体で使用できるようにその値を保存します。 このメモリ使用は、環境をキャプチャしないコードを実行するようなもっと一般的な場合には払いたくないオーバーヘッドです。 関数は、絶対に環境をキャプチャすることが許可されていないので、関数を定義して使えば、このオーバーヘッドを招くことは絶対にありません。 クロージャは、3つの方法で環境から値をキャプチャでき、この方法は関数が引数を取れる3つの方法に直に対応します: 所有権を奪う、可変で借用する、不変で借用するです。これらは、以下のように3つのFnトレイトでコード化されています: FnOnceは、クロージャの 環境 として知られている内包されたスコープからキャプチャした変数を消費します。 キャプチャした変数を消費するために、定義された際にクロージャはこれらの変数の所有権を奪い、 自身にムーブするのです。名前のうち、Onceの部分は、 このクロージャは同じ変数の所有権を2回以上奪うことができないという事実を表しているので、1回しか呼ぶことができないのです。 FnMutは、可変で値を借用するので、環境を変更することができます。 Fnは、環境から値を不変で借用します。 クロージャを生成する時、クロージャが環境を使用する方法に基づいて、コンパイラはどのトレイトを使用するか推論します。 少なくとも1回は呼び出されるので、全てのクロージャはFnOnceを実装しています。キャプチャした変数をムーブしないクロージャは、 FnMutも実装し、キャプチャした変数に可変でアクセスする必要のないクロージャは、Fnも実装しています。 リスト13-12では、equal_to_xクロージャはxを不変で借用しています(ゆえにequal_to_xはFnトレイトです)。 クロージャの本体は、xを読む必要しかないからです。 環境でクロージャが使用している値の所有権を奪うことをクロージャに強制したいなら、引数リストの前にmoveキーワードを使用できます。 このテクニックは、新しいスレッドにデータが所有されるように、クロージャを新しいスレッドに渡して、 データをムーブする際に大概は有用です。 並行性について語る第16章で、moveクロージャの例はもっと多く出てきます。とりあえず、 こちらがmoveキーワードがクロージャ定義に追加され、整数の代わりにベクタを使用するリスト13-12からのコードです。 整数はムーブではなく、コピーされてしまいますからね; このコードはまだコンパイルできないことに注意してください。 ファイル名: src/main.rs fn main() { let x = vec![1, 2, 3]; let equal_to_x = move |z| z == x; // ここでは、xを使用できません: {:?} println!(\"can't use x here: {:?}\", x); let y = vec![1, 2, 3]; assert!(equal_to_x(y));\n} 以下のようなエラーを受けます: error[E0382]: use of moved value: `x`\n(エラー: ムーブされた値の使用: `x`) --> src/main.rs:6:40 |\n4 | let equal_to_x = move |z| z == x; | -------- value moved (into closure) here (値はここで(クロージャに)ムーブされた)\n5 |\n6 | println!(\"can't use x here: {:?}\", x); | ^ value used here after move (ムーブ後、値はここで使用された) | = note: move occurs because `x` has type `std::vec::Vec`, which does not implement the `Copy` trait (注釈: `x`が`std::vec::Vec`という`Copy`トレイトを実装しない型のため、ムーブが起きました) クロージャが定義された際に、クロージャにxの値はムーブされています。moveキーワードを追加したからです。 そして、クロージャはxの所有権を持ち、mainがprintln!でxを使うことはもう叶わないのです。 println!を取り除けば、この例は修正されます。 Fnトレイトのどれかを指定するほとんどの場合、Fnから始めると、コンパイラがクロージャ本体内で起こっていることにより、 FnMutやFnOnceが必要な場合、教えてくれるでしょう。 環境をキャプチャできるクロージャが関数の引数として有用な場面を説明するために、次のトピックに移りましょう: イテレータです。","breadcrumbs":"関数型言語の機能:イテレータとクロージャ » クロージャで環境をキャプチャする","id":"237","title":"クロージャで環境をキャプチャする"},"238":{"body":"イテレータパターンにより、一連の要素に順番に何らかの作業を行うことができます。イテレータは、 各要素を繰り返し、シーケンスが終わったことを決定するロジックの責任を負います。イテレータを使用すると、 自身でそのロジックを再実装する必要がなくなるのです。 Rustにおいて、イテレータは 怠惰 です。つまり、イテレータを使い込んで消費するメソッドを呼ぶまで何の効果もないということです。 例えば、リスト13-13のコードは、Vecに定義されたiterメソッドを呼ぶことでv1ベクタの要素に対するイテレータを生成しています。 このコード単独では、何も有用なことはしません。 let v1 = vec![1, 2, 3]; let v1_iter = v1.iter(); リスト13-13: イテレータを生成する 一旦イテレータを生成したら、いろんな手段で使用することができます。第3章のリスト3-5では、 ここまでiterの呼び出しが何をするかごまかしてきましたが、forループでイテレータを使い、 各要素に何かコードを実行しています。 リスト13-14の例は、イテレータの生成とforループでイテレータを使用することを区別しています。 イテレータは、v1_iter変数に保存され、その時には繰り返しは起きていません。v1_iterのイテレータで、 forループが呼び出された時に、イテレータの各要素がループの繰り返しで使用され、各値が出力されます。 let v1 = vec![1, 2, 3]; let v1_iter = v1.iter(); for val in v1_iter { // {}でした println!(\"Got: {}\", val);\n} リスト13-14: forループでイテレータを使用する 標準ライブラリにより提供されるイテレータが存在しない言語では、変数を添え字0から始め、 その変数でベクタに添え字アクセスして値を得て、ベクタの総要素数に到達するまでループでその変数の値をインクリメントすることで、 この同じ機能を書く可能性が高いでしょう。 イテレータはそのロジック全てを処理してくれるので、めちゃくちゃにしてしまう可能性のあるコードの繰り返しを減らしてくれます。 イテレータにより、添え字を使えるデータ構造、ベクタなどだけではなく、多くの異なるシーケンスに対して同じロジックを使う柔軟性も得られます。 イテレータがそれをする方法を調査しましょう。","breadcrumbs":"関数型言語の機能:イテレータとクロージャ » 一連の要素をイテレータで処理する","id":"238","title":"一連の要素をイテレータで処理する"},"239":{"body":"全てのイテレータは、標準ライブラリで定義されているIteratorというトレイトを実装しています。 このトレイトの定義は、以下のようになっています: pub trait Iterator { type Item; fn next(&mut self) -> Option; // デフォルト実装のあるメソッドは省略 // methods with default implementations elided\n} この定義は新しい記法を使用していることに注目してください: type ItemとSelf::Itemで、 これらはこのトレイトとの 関連型 (associated type)を定義しています。関連型についての詳細は、第19章で語ります。 とりあえず、知っておく必要があることは、このコードがIteratorトレイトを実装するには、Item型も定義する必要があり、 そして、このItem型がnextメソッドの戻り値の型に使われていると述べていることです。換言すれば、 Item型がイテレータから返ってくる型になるだろうということです。 Iteratorトレイトは、一つのメソッドを定義することを実装者に要求することだけします: nextメソッドで、 これは1度にSomeに包まれたイテレータの1要素を返し、繰り返しが終わったら、Noneを返します。 イテレータに対して直接nextメソッドを呼び出すこともできます; リスト13-15は、 ベクタから生成されたイテレータのnextを繰り返し呼び出した時にどんな値が返るかを模擬しています。 ファイル名: src/lib.rs #[test]\nfn iterator_demonstration() { let v1 = vec![1, 2, 3]; let mut v1_iter = v1.iter(); assert_eq!(v1_iter.next(), Some(&1)); assert_eq!(v1_iter.next(), Some(&2)); assert_eq!(v1_iter.next(), Some(&3)); assert_eq!(v1_iter.next(), None);\n} リスト13-15: イテレータに対してnextメソッドを呼び出す v1_iterを可変にする必要があったことに注目してください: イテレータのnextメソッドを呼び出すと、 今シーケンスのどこにいるかを追いかけるためにイテレータが使用している内部の状態が変わります。 つまり、このコードはイテレータを 消費 、または使い込むのです。 nextの各呼び出しは、イテレータの要素を一つ、食います。forループを使用した時には、 v1_iterを可変にする必要はありませんでした。というのも、ループがv1_iterの所有権を奪い、 陰で可変にしていたからです。 また、nextの呼び出しで得られる値は、ベクタの値への不変な参照であることにも注目してください。 iterメソッドは、不変参照へのイテレータを生成します。v1の所有権を奪い、所有された値を返すイテレータを生成したいなら、 iterではなくinto_iterを呼び出すことができます。同様に、可変参照を繰り返したいなら、 iterではなくiter_mutを呼び出せます。","breadcrumbs":"関数型言語の機能:イテレータとクロージャ » Iteratorトレイトとnextメソッド","id":"239","title":"Iteratorトレイトとnextメソッド"},"24":{"body":"新しく作成したプログラムをちょうど実行したので、その途中の手順を調査しましょう。 Rustプログラムを実行する前に、以下のように、rustcコマンドを入力し、ソースファイルの名前を渡すことで、 Rustコンパイラを使用してコンパイルしなければなりません。 $ rustc main.rs あなたにCやC++の背景があるなら、これはgccやclangと似ていると気付くでしょう。コンパイルに成功後、 Rustはバイナリの実行可能ファイルを出力します。 Linux、macOS、WindowsのPowerShellなら、シェルで以下のようにlsコマンドを入力することで実行可能ファイルを見られます: $ ls\nmain main.rs WindowsのCMDなら、以下のように入力するでしょう: > dir /B %= the /B option says to only show the file names =% %= /Bオプションは、ファイル名だけを表示することを宣言する =%\nmain.exe\nmain.pdb\nmain.rs これは、 .rs 拡張子のソースコードファイル、実行可能ファイル(Windowsなら main.exe 、他のプラットフォームでは、 main )、 そして、CMDを使用しているなら、 .pdb 拡張子のデバッグ情報を含むファイルを表示します。ここから、 main か main.exe を走らせます。このように: $ ./main # or .\\main.exe on Windows # または、Widnowsなら.\\main.exe main.rs がHello, world!プログラムなら、この行はHello, world!と端末に出力するでしょう。 RubyやPython、JavaScriptなどの動的言語により造詣が深いなら、プログラムのコンパイルと実行を個別の手順で行うことに慣れていない可能性があります。 Rustは AOTコンパイル (ahead-of-time; 訳注: 予め)言語です。つまり、プログラムをコンパイルし、 実行可能ファイルを誰かにあげ、あげた人がRustをインストールしていなくても実行できるわけです。 誰かに .rb 、 .py 、 .js ファイルをあげたら、それぞれRuby、Python、JavaScriptの実装がインストールされている必要があります。 ですが、そのような言語では、プログラムをコンパイルし実行するには、1コマンドしか必要ないのです。 全ては言語設計においてトレードオフなのです。 簡単なプログラムならrustcでコンパイルするだけでも十分ですが、プロジェクトが肥大化してくると、 オプションを全て管理し、自分のコードを簡単に共有したくなるでしょう。次は、Cargoツールを紹介します。 これは、現実世界のRustプログラムを書く手助けをしてくれるでしょう。","breadcrumbs":"事始め » コンパイルと実行は個別のステップ","id":"24","title":"コンパイルと実行は個別のステップ"},"240":{"body":"Iteratorトレイトには、標準ライブラリが提供してくれているデフォルト実装のある多くの異なるメソッドがあります; Iteratorトレイトの標準ライブラリのAPIドキュメントを検索することで、これらのメソッドについて知ることができます。 これらのメソッドの中には、定義内でnextメソッドを呼ぶものもあり、故にIteratorトレイトを実装する際には、 nextメソッドを実装する必要があるのです。 nextを呼び出すメソッドは、 消費アダプタ (consuming adaptors)と呼ばれます。呼び出しがイテレータの使い込みになるからです。 一例は、sumメソッドで、これはイテレータの所有権を奪い、nextを繰り返し呼び出すことで要素を繰り返し、 故にイテレータを消費するのです。繰り返しが進むごとに、各要素を一時的な合計に追加し、 繰り返しが完了したら、その合計を返します。リスト13-16は、sumの使用を説明したテストです: ファイル名: src/lib.rs #[test]\nfn iterator_sum() { let v1 = vec![1, 2, 3]; let v1_iter = v1.iter(); let total: i32 = v1_iter.sum(); assert_eq!(total, 6);\n} リスト13-16: sumメソッドを呼び出してイテレータの全要素の合計を得る sumは呼び出し対象のイテレータの所有権を奪うので、sum呼び出し後にv1_iterを使用することはできません。","breadcrumbs":"関数型言語の機能:イテレータとクロージャ » イテレータを消費するメソッド","id":"240","title":"イテレータを消費するメソッド"},"241":{"body":"Iteratorトレイトに定義された他のメソッドは、 イテレータアダプタ (iterator adaptors)として知られていますが、 イテレータを別の種類のイテレータに変えさせてくれます。イテレータアダプタを複数回呼ぶ呼び出しを連結して、 複雑な動作を読みやすい形で行うことができます。ですが、全てのイテレータは怠惰なので、消費アダプタメソッドのどれかを呼び出し、 イテレータアダプタの呼び出しから結果を得なければなりません。 リスト13-17は、イテレータアダプタメソッドのmapの呼び出し例を示し、各要素に対して呼び出すクロージャを取り、 新しいイテレータを生成します。ここのクロージャは、ベクタの各要素が1インクリメントされる新しいイテレータを作成します。 ところが、このコードは警告を発します: ファイル名: src/main.rs let v1: Vec = vec![1, 2, 3]; v1.iter().map(|x| x + 1); リスト13-17: イテレータアダプタのmapを呼び出して新規イテレータを作成する 出る警告は以下の通りです: warning: unused `std::iter::Map` which must be used: iterator adaptors are lazy\nand do nothing unless consumed\n(警告: 使用されねばならない`std::iter::Map`が未使用です: イテレータアダプタは怠惰で、\n消費されるまで何もしません) --> src/main.rs:4:5 |\n4 | v1.iter().map(|x| x + 1); | ^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: #[warn(unused_must_use)] on by default リスト13-17のコードは何もしません; 指定したクロージャは、決して呼ばれないのです。警告が理由を思い出させてくれています: イテレータアダプタは怠惰で、ここでイテレータを消費する必要があるのです。 これを修正し、イテレータを消費するには、collectメソッドを使用しますが、これは第12章のリスト12-1でenv::argsとともに使用しました。 このメソッドはイテレータを消費し、結果の値をコレクションデータ型に集結させます。 リスト13-18において、map呼び出しから返ってきたイテレータを繰り返した結果をベクタに集結させています。 このベクタは、最終的に元のベクタの各要素に1を足したものが含まれます。 ファイル名: src/main.rs let v1: Vec = vec![1, 2, 3]; let v2: Vec<_> = v1.iter().map(|x| x + 1).collect(); assert_eq!(v2, vec![2, 3, 4]); リスト13-18: mapメソッドを呼び出して新規イテレータを作成し、 それからcollectメソッドを呼び出してその新規イテレータを消費し、ベクタを生成する mapはクロージャを取るので、各要素に対して行いたいどんな処理も指定することができます。 これは、Iteratorトレイトが提供する繰り返し動作を再利用しつつ、 クロージャにより一部の動作をカスタマイズできる好例になっています。","breadcrumbs":"関数型言語の機能:イテレータとクロージャ » 他のイテレータを生成するメソッド","id":"241","title":"他のイテレータを生成するメソッド"},"242":{"body":"イテレータが出てきたので、filterイテレータアダプタを使って環境をキャプチャするクロージャの一般的な使用をデモすることができます。 イテレータのfilterメソッドは、イテレータの各要素を取り、論理値を返すクロージャを取ります。 このクロージャがtrueを返せば、filterが生成するイテレータにその値が含まれます。クロージャがfalseを返したら、 結果のイテレータにその値は含まれません。 リスト13-19では、環境からshoe_size変数をキャプチャするクロージャでfilterを使って、 Shoe構造体インスタンスのコレクションを繰り返しています。指定したサイズの靴だけを返すわけです。 ファイル名: src/lib.rs #[derive(PartialEq, Debug)]\nstruct Shoe { size: u32, style: String,\n} fn shoes_in_my_size(shoes: Vec, shoe_size: u32) -> Vec { shoes.into_iter() .filter(|s| s.size == shoe_size) .collect()\n} #[test]\nfn filters_by_size() { let shoes = vec![ Shoe { size: 10, style: String::from(\"sneaker\") }, Shoe { size: 13, style: String::from(\"sandal\") }, Shoe { size: 10, style: String::from(\"boot\") }, ]; let in_my_size = shoes_in_my_size(shoes, 10); assert_eq!( in_my_size, vec![ Shoe { size: 10, style: String::from(\"sneaker\") }, Shoe { size: 10, style: String::from(\"boot\") }, ] );\n} リスト13-19: shoe_sizeをキャプチャするクロージャでfilterメソッドを使用する shoes_in_my_size関数は、引数として靴のベクタとサイズの所有権を奪います。指定されたサイズの靴だけを含むベクタを返します。 shoes_in_my_sizeの本体で、into_iterを呼び出してベクタの所有権を奪うイテレータを作成しています。 そして、filterを呼び出してそのイテレータをクロージャがtrueを返した要素だけを含む新しいイテレータに適合させます。 クロージャは、環境からshoe_size引数をキャプチャし、指定されたサイズの靴だけを保持しながら、 その値を各靴のサイズと比較します。最後に、collectを呼び出すと、 関数により返ってきたベクタに適合させたイテレータから返ってきた値が集まるのです。 shoes_in_my_sizeを呼び出した時に、指定した値と同じサイズの靴だけが得られることをテストは示しています。","breadcrumbs":"関数型言語の機能:イテレータとクロージャ » 環境をキャプチャするクロージャを使用する","id":"242","title":"環境をキャプチャするクロージャを使用する"},"243":{"body":"ベクタに対し、iter、into_iter、iter_mutを呼び出すことでイテレータを作成できることを示してきました。 ハッシュマップなどの標準ライブラリの他のコレクション型からもイテレータを作成できます。 Iteratorトレイトを自分で実装することで、したいことを何でもするイテレータを作成することもできます。 前述の通り、定義を提供する必要のある唯一のメソッドは、nextメソッドなのです。一旦、そうしてしまえば、 Iteratorトレイトが用意しているデフォルト実装のある他の全てのメソッドを使うことができるのです! デモ用に、絶対に1から5をカウントするだけのイテレータを作成しましょう。まず、値を保持する構造体を生成し、 Iteratorトレイトを実装することでこの構造体をイテレータにし、その実装内の値を使用します。 リスト13-20は、Counter構造体とCounterのインスタンスを作るnew関連関数の定義です: ファイル名: src/lib.rs struct Counter { count: u32,\n} impl Counter { fn new() -> Counter { Counter { count: 0 } }\n} リスト13-20: Counter構造体とcountに対して0という初期値でCounterのインスタンスを作るnew関数を定義する Counter構造体には、countというフィールドがあります。このフィールドは、 1から5までの繰り返しのどこにいるかを追いかけるu32値を保持しています。Counterの実装にその値を管理してほしいので、 countフィールドは非公開です。countフィールドは常に0という値から新規インスタンスを開始するという動作をnew関数は強要します。 次に、nextメソッドの本体をこのイテレータが使用された際に起きてほしいことを指定するように定義して、 Counter型に対してIteratorトレイトを実装します。リスト13-21のようにですね: ファイル名: src/lib.rs # struct Counter {\n# count: u32,\n# }\n#\nimpl Iterator for Counter { type Item = u32; fn next(&mut self) -> Option { self.count += 1; if self.count < 6 { Some(self.count) } else { None } }\n} リスト13-21: Counter構造体にIteratorトレイトを実装する イテレータのItem関連型をu32に設定しました。つまり、イテレータは、u32の値を返します。 ここでも、まだ関連型について心配しないでください。第19章で講義します。 イテレータに現在の状態に1を足してほしいので、まず1を返すようにcountを0に初期化しました。 countの値が5以下なら、nextはSomeに包まれた現在の値を返しますが、 countが6以上なら、イテレータはNoneを返します。 Counterイテレータのnextメソッドを使用する 一旦Iteratorトレイトを実装し終わったら、イテレータの出来上がりです!リスト13-22は、 リスト13-15のベクタから生成したイテレータと全く同様に、直接nextメソッドを呼び出すことで、 Counter構造体のイテレータ機能を使用できることをデモするテストを示しています。 ファイル名: src/lib.rs # struct Counter {\n# count: u32,\n# }\n#\n# impl Iterator for Counter {\n# type Item = u32;\n#\n# fn next(&mut self) -> Option {\n# self.count += 1;\n#\n# if self.count < 6 {\n# Some(self.count)\n# } else {\n# None\n# }\n# }\n# }\n#\n#[test]\nfn calling_next_directly() { let mut counter = Counter::new(); assert_eq!(counter.next(), Some(1)); assert_eq!(counter.next(), Some(2)); assert_eq!(counter.next(), Some(3)); assert_eq!(counter.next(), Some(4)); assert_eq!(counter.next(), Some(5)); assert_eq!(counter.next(), None);\n} リスト13-22: nextメソッド実装の機能をテストする このテストは、counter変数に新しいCounterインスタンスを生成し、 それからイテレータにほしい動作が実装し終わっていることを実証しながら、nextを繰り返し呼び出しています: 1から5の値を返すことです。 他のIteratorトレイトメソッドを使用する nextメソッドを定義してIteratorトレイトを実装したので、今では、標準ライブラリで定義されているように、 どんなIteratorトレイトメソッドのデフォルト実装も使えるようになりました。全てnextメソッドの機能を使っているからです。 例えば、何らかの理由で、Counterインスタンスが生成する値を取り、最初の値を飛ばしてから、 別のCounterインスタンスが生成する値と一組にし、各ペアを掛け算し、3で割り切れる結果だけを残し、 全結果の値を足し合わせたくなったら、リスト13-23のテストに示したように、そうすることができます: ファイル名: src/lib.rs # struct Counter {\n# count: u32,\n# }\n#\n# impl Counter {\n# fn new() -> Counter {\n# Counter { count: 0 }\n# }\n# }\n#\n# impl Iterator for Counter {\n# // このイテレータはu32を生成します\n# // Our iterator will produce u32s\n# type Item = u32;\n#\n# fn next(&mut self) -> Option {\n# // カウントをインクリメントする。故に0から始まる\n# // increment our count. This is why we started at zero.\n# self.count += 1;\n#\n# // カウントが終わったかどうか確認する\n# // check to see if we've finished counting or not.\n# if self.count < 6 {\n# Some(self.count)\n# } else {\n# None\n# }\n# }\n# }\n#\n#[test]\nfn using_other_iterator_trait_methods() { let sum: u32 = Counter::new().zip(Counter::new().skip(1)) .map(|(a, b)| a * b) .filter(|x| x % 3 == 0) .sum(); assert_eq!(18, sum);\n} リスト13-23: Counterイテレータに対していろんなIteratorトレイトのメソッドを使用する zipは4組しか生成しないことに注意してください; 理論的な5番目の組の(5, None)は、 入力イテレータのどちらかがNoneを返したら、zipはNoneを返却するため、決して生成されることはありません。 nextメソッドの動作方法を指定し、標準ライブラリがnextを呼び出す他のメソッドにデフォルト実装を提供しているので、 これらのメソッド呼び出しは全て可能です。","breadcrumbs":"関数型言語の機能:イテレータとクロージャ » Iteratorトレイトで独自のイテレータを作成する","id":"243","title":"Iteratorトレイトで独自のイテレータを作成する"},"244":{"body":"このイテレータに関する新しい知識があれば、イテレータを使用してコードのいろんな場所をより明確で簡潔にすることで、 第12章の入出力プロジェクトを改善することができます。イテレータがConfig::new関数とsearch関数の実装を改善する方法に目を向けましょう。","breadcrumbs":"関数型言語の機能:イテレータとクロージャ » 入出力プロジェクトを改善する","id":"244","title":"入出力プロジェクトを改善する"},"245":{"body":"リスト12-6において、スライスに添え字アクセスして値をクローンすることで、Config構造体に値を所有させながら、 String値のスライスを取り、Config構造体のインスタンスを作るコードを追記しました。リスト13-24では、 リスト12-23のようなConfig::newの実装を再現しました: ファイル名: src/lib.rs impl Config { pub fn new(args: &[String]) -> Result { if args.len() < 3 { return Err(\"not enough arguments\"); } let query = args[1].clone(); let filename = args[2].clone(); let case_sensitive = env::var(\"CASE_INSENSITIVE\").is_err(); Ok(Config { query, filename, case_sensitive }) }\n} リスト13-24: リスト12-23からConfig::new関数の再現 その際、将来的に除去する予定なので、非効率的なclone呼び出しを憂慮するなと述べました。 えっと、その時は今です! 引数argsにString要素のスライスがあるためにここでcloneが必要だったのですが、 new関数はargsを所有していません。Configインスタンスの所有権を返すためには、 Configインスタンスがその値を所有できるように、Configのqueryとfilenameフィールドから値をクローンしなければなりませんでした。 イテレータについての新しい知識があれば、new関数をスライスを借用する代わりに、 引数としてイテレータの所有権を奪うように変更することができます。スライスの長さを確認し、 特定の場所に添え字アクセスするコードの代わりにイテレータの機能を使います。これにより、 イテレータは値にアクセスするので、Config::new関数がすることが明確化します。 ひとたび、Config::newがイテレータの所有権を奪い、借用する添え字アクセス処理をやめたら、 cloneを呼び出して新しくメモリ確保するのではなく、イテレータからのString値をConfigにムーブできます。 返却されるイテレータを直接使う 入出力プロジェクトの src/main.rs ファイルを開いてください。こんな見た目のはずです: ファイル名: src/main.rs fn main() { let args: Vec = env::args().collect(); let config = Config::new(&args).unwrap_or_else(|err| { eprintln!(\"Problem parsing arguments: {}\", err); process::exit(1); }); // --snip--\n} リスト12-24のようなmain関数の冒頭をリスト13-25のコードに変更します。 これは、Config::newも更新するまでコンパイルできません。 ファイル名: src/main.rs fn main() { let config = Config::new(env::args()).unwrap_or_else(|err| { eprintln!(\"Problem parsing arguments: {}\", err); process::exit(1); }); // --snip--\n} リスト13-25: env::argsの戻り値をConfig::newに渡す env::args関数は、イテレータを返します!イテレータの値をベクタに集結させ、それからスライスをConfig::newに渡すのではなく、 今ではenv::argsから返ってくるイテレータの所有権を直接Config::newに渡しています。 次に、Config::newの定義を更新する必要があります。入出力プロジェクトの src/lib.rs ファイルで、 Config::newのシグニチャをリスト13-26のように変えましょう。関数本体を更新する必要があるので、 それでもコンパイルはできません。 ファイル名: src/lib.rs impl Config { pub fn new(mut args: std::env::Args) -> Result { // --snip-- リスト13-26: Config::newのシグニチャをイテレータを期待するように更新する env::args関数の標準ライブラリドキュメントは、自身が返すイテレータの型は、std::env::Argsであると表示しています。 Config::new関数のシグニチャを更新したので、引数argsの型は、&[String]ではなく、 std::env::Argsになりました。argsの所有権を奪い、繰り返しを行うことでargsを可変化する予定なので、 args引数の仕様にmutキーワードを追記でき、可変にします。 添え字の代わりにIteratorトレイトのメソッドを使用する 次に、Config::newの本体を修正しましょう。標準ライブラリのドキュメントは、 std::env::ArgsがIteratorトレイトを実装していることにも言及しているので、 それに対してnextメソッドを呼び出せることがわかります!リスト13-27は、 リスト12-23のコードをnextメソッドを使用するように更新したものです: ファイル名: src/lib.rs # fn main() {}\n# use std::env;\n#\n# struct Config {\n# query: String,\n# filename: String,\n# case_sensitive: bool,\n# }\n#\nimpl Config { pub fn new(mut args: std::env::Args) -> Result { args.next(); let query = match args.next() { Some(arg) => arg, // クエリ文字列を取得しませんでした None => return Err(\"Didn't get a query string\"), }; let filename = match args.next() { Some(arg) => arg, // ファイル名を取得しませんでした None => return Err(\"Didn't get a file name\"), }; let case_sensitive = env::var(\"CASE_INSENSITIVE\").is_err(); Ok(Config { query, filename, case_sensitive }) }\n} リスト13-27: Config::newの本体をイテレータメソッドを使うように変更する env::argsの戻り値の1番目の値は、プログラム名であることを思い出してください。それは無視し、 次の値を取得したいので、まずnextを呼び出し、戻り値に対して何もしません。2番目に、 nextを呼び出してConfigのqueryフィールドに置きたい値を得ます。nextがSomeを返したら、 matchを使用してその値を抜き出します。Noneを返したら、十分な引数が与えられなかったということなので、 Err値で早期リターンします。filename値に対しても同じことをします。","breadcrumbs":"関数型言語の機能:イテレータとクロージャ » イテレータを使用してcloneを取り除く","id":"245","title":"イテレータを使用してcloneを取り除く"},"246":{"body":"入出力プロジェクトのsearch関数でも、イテレータを活用することができます。その関数はリスト12-19に示していますが、以下のリスト13-28に再掲します。 ファイル名: src/lib.rs pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { let mut results = Vec::new(); for line in contents.lines() { if line.contains(query) { results.push(line); } } results\n} リスト13-28: リスト12-19のsearch関数の実装 イテレータアダプタメソッドを使用して、このコードをもっと簡潔に書くことができます。そうすれば、 可変な中間のresultsベクタをなくすこともできます。関数型プログラミングスタイルは、可変な状態の量を最小化することを好み、 コードを明瞭化します。可変な状態を除去すると、検索を同時並行に行うという将来的な改善をするのが、 可能になる可能性があります。なぜなら、resultsベクタへの同時アクセスを管理する必要がなくなるからです。 リスト13-29は、この変更を示しています: ファイル名: src/lib.rs pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { contents.lines() .filter(|line| line.contains(query)) .collect()\n} リスト13-29: search関数の実装でイテレータアダプタのメソッドを使用する search関数の目的は、queryを含むcontentsの行全てを返すことであることを思い出してください。 リスト13-19のfilter例に酷似して、このコードはfilterアダプタを使用してline.contains(query)が真を返す行だけを残すことができます。 それから、合致した行を別のベクタにcollectで集結させます。ずっと単純です!ご自由に、 同じ変更を行い、search_case_insensitive関数でもイテレータメソッドを使うようにしてください。 次の論理的な疑問は、自身のコードでどちらのスタイルを選ぶかと理由です: リスト13-28の元の実装とリスト13-29のイテレータを使用するバージョンです。 多くのRustプログラマは、イテレータスタイルを好みます。とっかかりが少し困難ですが、 いろんなイテレータアダプタとそれがすることの感覚を一度掴めれば、イテレータの方が理解しやすいこともあります。 いろんなループを少しずつもてあそんだり、新しいベクタを構築する代わりに、コードは、ループの高難度の目的に集中できるのです。 これは、ありふれたコードの一部を抽象化するので、イテレータの各要素が通過しなければならないふるい条件など、 このコードに独特の概念を理解しやすくなります。 ですが、本当に2つの実装は等価なのでしょうか?直観的な仮説は、より低レベルのループの方がより高速ということかもしれません。 パフォーマンスに触れましょう。","breadcrumbs":"関数型言語の機能:イテレータとクロージャ » イテレータアダプタでコードをより明確にする","id":"246","title":"イテレータアダプタでコードをより明確にする"},"247":{"body":"ループを使うべきかイテレータを使うべきか決定するために、search関数のうち、どちらのバージョンが速いか知る必要があります: 明示的なforループがあるバージョンと、イテレータのバージョンです。 サー・アーサー・コナン・ドイル(Sir Arthur Conan Doyle)の、 シャーロックホームズの冒険 (The Adventures of Sherlock Homes)全体をStringに読み込み、 そのコンテンツで the という単語を検索することでベンチマークを行いました。 こちらが、forを使用したsearch関数のバージョンと、イテレータを使用したバージョンに関するベンチマーク結果です。 test bench_search_for ... bench: 19,620,300 ns/iter (+/- 915,700)\ntest bench_search_iter ... bench: 19,234,900 ns/iter (+/- 657,200) イテレータバージョンの方が些(いささ)か高速ですね!ここでは、ベンチマークのコードは説明しません。 なぜなら、要点は、2つのバージョンが等価であることを証明することではなく、 これら2つの実装がパフォーマンス的にどう比較されるかを大まかに把握することだからです。 より理解しやすいベンチマークには、いろんなサイズの様々なテキストをcontentsとして、異なる単語、異なる長さの単語をqueryとして、 他のあらゆる種類のバリエーションを確認するべきです。重要なのは: イテレータは、 高度な抽象化にも関わらず、低レベルのコードを自身で書いているかのように、ほぼ同じコードにコンパイルされることです。 イテレータは、Rustの ゼロコスト抽象化 の一つであり、これは、抽象化を使うことが追加の実行時オーバーヘッドを生まないことを意味しています。 このことは、C++の元の設計者であり実装者のビャーネ・ストロヴストルップ(Bjarne Stroustrup)が、 ゼロオーバーヘッド を「C++の基礎(2012)」で定義したのと類似しています。 一般的に、C++の実装は、ゼロオーバーヘッド原則を遵守します: 使用しないものには、支払わなくてよい。 さらに: 実際に使っているものに対して、コードをそれ以上うまく渡すことはできない。 別の例として、以下のコードは、オーディオデコーダから取ってきました。デコードアルゴリズムは、 線形予測数学演算を使用して、以前のサンプルの線形関数に基づいて未来の値を予測します。このコードは、 イテレータ連結をしてスコープにある3つの変数に計算を行っています: bufferというデータのスライス、 12のcoefficients(係数)の配列、qlp_shiftでデータをシフトする量です。この例の中で変数を宣言しましたが、 値は与えていません; このコードは、文脈の外では大して意味を持ちませんが、 それでもRustが高レベルな考えを低レベルなコードに翻訳する簡潔で現実的な例になっています: let buffer: &mut [i32];\nlet coefficients: [i64; 12];\nlet qlp_shift: i16; for i in 12..buffer.len() { let prediction = coefficients.iter() .zip(&buffer[i - 12..i]) .map(|(&c, &s)| c * s as i64) .sum::() >> qlp_shift; let delta = buffer[i]; buffer[i] = prediction as i32 + delta;\n} predictionの値を算出するために、このコードは、coefficientsの12の値を繰り返し、zipメソッドを使用して、 係数値を前のbufferの12の値と組にします。それから各組について、その値をかけ合わせ、結果を全て合計し、 合計のビットをqlp_shiftビット分だけ右にシフトさせます。 オーディオデコーダのようなアプリケーションの計算は、しばしばパフォーマンスに最も重きを置きます。 ここでは、イテレータを作成し、2つのアダプタを使用し、それから値を消費しています。 このRustコードは、どんな機械語コードにコンパイルされるのでしょうか?えー、執筆時点では、 手作業で書いたものと同じ機械語にコンパイルされます。coefficientsの値の繰り返しに対応するループは全く存在しません: コンパイラは、12回繰り返しがあることを把握しているので、ループを「展開」します。 ループの展開 は、ループ制御コードのオーバーヘッドを除去し、代わりにループの繰り返しごとに同じコードを生成する最適化です。 係数は全てレジスタに保存されます。つまり、値に非常に高速にアクセスします。実行時に配列の境界チェックをすることもありません。 コンパイラが適用可能なこれらの最適化全てにより、結果のコードは究極的に効率化されます。このことがわかったので、 もうイテレータとクロージャを恐れなしに使用することができますね!それらのおかげでコードは、高レベルだけれども、 そうすることに対して実行時のパフォーマンスを犠牲にしないようになります。","breadcrumbs":"関数型言語の機能:イテレータとクロージャ » パフォーマンス比較: ループVSイテレータ","id":"247","title":"パフォーマンス比較: ループVSイテレータ"},"248":{"body":"クロージャとイテレータは、関数型言語の考えに着想を得たRustの機能です。低レベルのパフォーマンスで、 高レベルの考えを明確に表現するというRustの能力に貢献しています。クロージャとイテレータの実装は、 実行時のパフォーマンスが影響されないようなものです。これは、ゼロ代償抽象化を提供するのに努力を惜しまないRustの目標の一部です。 今や入出力プロジェクトの表現力を改善したので、プロジェクトを世界と共有するのに役に立つcargoの機能にもっと目を向けましょう。","breadcrumbs":"関数型言語の機能:イテレータとクロージャ » まとめ","id":"248","title":"まとめ"},"249":{"body":"今までCargoのビルド、実行、コードのテストを行うという最も基礎的な機能のみを使ってきましたが、 他にもできることはたくさんあります。この章では、そのような他のより高度な機能の一部を議論し、 以下のことをする方法をお見せしましょう: リリースプロファイルでビルドをカスタマイズする crates.io でライブラリを公開する ワークスペースで巨大なプロジェクトを体系化する crates.io からバイナリをインストールする 独自のコマンドを使用してCargoを拡張する また、Cargoはこの章で講義する以上のこともできるので、機能の全解説を見るには、 ドキュメンテーション を参照されたし。","breadcrumbs":"CargoとCrates.ioについてより詳しく","id":"249","title":"CargoとCrates.ioについてより詳しく"},"25":{"body":"Cargoは、Rustのビルドシステム兼、パッケージマネージャです。ほとんどのRustaceanはこのツールを使用して、 Rustプロジェクトの管理をしています。Cargoは、コードのビルドやコードが依存しているライブラリのダウンロード、 それらのライブラリのビルド(コードが必要とするライブラリを我々は、 依存 と呼んでいます)などの多くの仕事を扱ってくれるからです。 今までに書いたような最も単純なRustプログラムは、依存がありません。従って、Hello, world!プロジェクトをCargoを使ってビルドしても、 Cargoのコードをビルドする部分しか使用しないでしょう。より複雑なRustプログラムを書くにつれて、 依存を追加し、Cargoでプロジェクトを開始したら、依存の追加は、遥かに簡単になるのです。 Rustプロジェクトの大多数がCargoを使用しているので、これ以降この本では、あなたもCargoを使用していることを想定します。 Cargoは、「インストール」節で議論した公式のインストーラを使用していれば、勝手にインストールされます。 Rustを他の何らかの手段でインストールした場合、以下のコマンドを端末に入れてCargoがインストールされているか確かめてください: $ cargo --version バージョンナンバーが見えたら、インストールされています!command not foundなどのエラーが見えたら、 自分のインストール方法をドキュメンテーションで確認して、Cargoを個別にインストールする方法を決定してください。","breadcrumbs":"事始め » Hello, Cargo!","id":"25","title":"Hello, Cargo!"},"250":{"body":"Rustにおいて、 リリースプロファイル とは、プログラマがコードのコンパイルオプションについてより制御可能にしてくれる、 定義済みのカスタマイズ可能なプロファイルです。各プロファイルは、それぞれ独立して設定されます。 Cargoには2つの主なプロファイルが存在します: devプロファイルは、cargo buildコマンドを実行したときに使用され、 releaseプロファイルは、cargo build --releaseコマンドを実行したときに使用されます。 devプロファイルは、開発中に役に立つデフォルト設定がなされており、releaseプロファイルは、 リリース用の設定がなされています。 これらのプロファイル名は、ビルドの出力で馴染みのある可能性があります: $ cargo build Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs\n$ cargo build --release Finished release [optimized] target(s) in 0.0 secs このビルド出力で表示されているdevとreleaseは、コンパイラが異なるプロファイルを使用していることを示しています。 プロジェクトの Cargo.toml ファイルに[profile.*]セクションが存在しない際に適用される各プロファイル用のデフォルト設定が、 Cargoには存在します。カスタマイズしたいプロファイル用の[profile.*]セクションを追加することで、 デフォルト設定の一部を上書きすることができます。例えば、こちらがdevとreleaseプロファイルのopt-level設定のデフォルト値です: ファイル名: Cargo.toml [profile.dev]\nopt-level = 0 [profile.release]\nopt-level = 3 opt-level設定は、0から3の範囲でコンパイラがコードに適用する最適化の度合いを制御します。 最適化を多くかけると、コンパイル時間が延びるので、開発中に頻繁にコードをコンパイルするのなら、 たとえ出力結果のコードの動作速度が遅くなっても早くコンパイルが済んでほしいですよね。 これが、devのopt-levelのデフォルト設定が0になっている唯一の理由です。 コードのリリース準備ができたら、より長い時間をコンパイルにかけるのが最善の策です。 リリースモードでコンパイルするのはたった1回ですが、コンパイル結果のプログラムは何度も実行するので、 リリースモードでは、長いコンパイル時間と引き換えに、生成したコードが速く動作します。 そのため、releaseのopt-levelのデフォルト設定が3になっているのです。 デフォルト設定に対してCargo.tomlで異なる値を追加すれば、上書きすることができます。 例として、開発用プロファイルで最適化レベル1を使用したければ、以下の2行をプロジェクトの Cargo.toml ファイルに追加できます: ファイル名: Cargo.toml [profile.dev]\nopt-level = 1 このコードは、デフォルト設定の0を上書きします。こうすると、cargo buildを実行したときに、 devプロファイル用のデフォルト設定に加えて、Cargoはopt-levelの変更を適用します。 opt-levelを1に設定したので、Cargoはデフォルトよりは最適化を行いますが、リリースビルドほどではありません。 設定の選択肢と各プロファイルのデフォルト設定の一覧は、 Cargoのドキュメンテーション を参照されたし。","breadcrumbs":"CargoとCrates.ioについてより詳しく » リリースプロファイルでビルドをカスタマイズする","id":"250","title":"リリースプロファイルでビルドをカスタマイズする"},"251":{"body":"プロジェクトの依存として crates.io のパッケージを使用しましたが、 自分のパッケージを公開することで他の人とコードを共有することもできます。 crates.io のクレート登録所は、自分のパッケージのソースコードを配布するので、 主にオープンソースのコードをホストします。 RustとCargoは、公開したパッケージを人が使用し、そもそも見つけやすくしてくれる機能を有しています。 これらの機能の一部を次に語り、そして、パッケージの公開方法を説明します。","breadcrumbs":"CargoとCrates.ioについてより詳しく » Crates.ioにクレートを公開する","id":"251","title":"Crates.ioにクレートを公開する"},"252":{"body":"パッケージを正確にドキュメントすることで、他のユーザがパッケージを使用する方法や、いつ使用すべきかを理解する手助けをすることになるので、 ドキュメンテーションを書くことに時間を費やす価値があります。第3章で、2連スラッシュ、//でRustのコードにコメントをつける方法を議論しました。 Rustには、ドキュメンテーション用のコメントも用意されていて、便利なことに ドキュメンテーションコメント として知られ、 HTMLドキュメントを生成します。クレートの 実装 法とは対照的にクレートの 使用 法を知ることに興味のあるプログラマ向けの、 公開API用のドキュメンテーションコメントの中身をこのHTMLは表示します。 ドキュメンテーションコメントは、2つではなく、3連スラッシュ、///を使用し、テキストを整形するMarkdown記法もサポートしています。 ドキュメント対象の要素の直前にドキュメンテーションコメントを配置してください。 リスト14-1は、my_crateという名のクレートのadd_one関数用のドキュメンテーションコメントを示しています: ファイル名: src/lib.rs /// Adds one to the number given.\n/// 与えられた数値に1を足す。\n///\n/// # Examples\n///\n/// ```\n/// let five = 5;\n///\n/// assert_eq!(6, my_crate::add_one(5));\n/// ```\npub fn add_one(x: i32) -> i32 { x + 1\n} リスト14-1: 関数のドキュメンテーションコメント ここで、add_one関数がすることの説明を与え、Examplesというタイトルでセクションを開始し、 add_one関数の使用法を模擬するコードを提供しています。このドキュメンテーションコメントからcargo docを実行することで、 HTMLドキュメントを生成することができます。このコマンドはコンパイラとともに配布されているrustdocツールを実行し、 生成されたHTMLドキュメントを target/doc ディレクトリに配置します。 利便性のために、cargo doc --openを走らせれば、現在のクレートのドキュメント用のHTML(と、 自分のクレートが依存している全てのドキュメント)を構築し、その結果をWebブラウザで開きます。 add_one関数まで下り、図14-1に示したように、ドキュメンテーションコメントのテキストがどう描画されるかを確認しましょう: 図14-1: add_one関数のHTMLドキュメント よく使われるセクション # Examplesマークダウンのタイトルをリスト14-1で使用し、「例」というタイトルのセクションをHTMLに生成しました。 こちらがこれ以外にドキュメントでよくクレート筆者が使用するセクションです: Panics : ドキュメント対象の関数がpanic!する可能性のある筋書きです。プログラムをパニックさせたくない関数の使用者は、 これらの状況で関数が呼ばれないことを確かめる必要があります。 Errors : 関数がResultを返すなら、起きうるエラーの種類とどんな条件がそれらのエラーを引き起こす可能性があるのか解説すると、 呼び出し側の役に立つので、エラーの種類によって処理するコードを変えて書くことができます。 Safety : 関数が呼び出すのにunsafe(unsafeについては第19章で議論します)なら、 関数がunsafeな理由を説明し、関数が呼び出し元に保持していると期待する不変条件を講義するセクションがあるべきです。 多くのドキュメンテーションコメントでは、これら全てのセクションが必要になることはありませんが、 これは自分のコードを呼び出している人が知りたいと思うコードの方向性を思い出させてくれるいいチェックリストになります。 テストとしてのドキュメンテーションコメント ドキュメンテーションコメントに例のコードブロックを追加すると、ライブラリの使用方法のデモに役立ち、 おまけもついてきます: cargo testを走らせると、ドキュメントのコード例をテストとして実行するのです! 例付きのドキュメントに上回るものはありません。しかし、ドキュメントが書かれてからコードが変更されたがために、 動かない例がついているよりも悪いものもありません。リスト14-1からadd_one関数のドキュメンテーションとともに、 cargo testを走らせたら、テスト結果に以下のような区域が見られます: Doc-tests my_crate running 1 test\ntest src/lib.rs - add_one (line 5) ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out さて、例のassert_eq!がパニックするように、関数か例を変更し、再度cargo testを実行したら、 docテストが、例とコードがお互いに同期されていないことを捕捉するところを目撃するでしょう! 含まれている要素にコメントする docコメントの別スタイル、//!は、コメントに続く要素にドキュメンテーションを付け加えるのではなく、 コメントを含む要素にドキュメンテーションを付け加えます。典型的には、クレートのルートファイル(慣例的には、 src/lib.rs )内部や、 モジュールの内部で使用して、クレートやモジュール全体にドキュメントをつけます。 例えば、add_one関数を含むmy_crateクレートの目的を解説するドキュメンテーションを追加したいのなら、 //!で始まるドキュメンテーションコメントを src/lib.rs ファイルの先頭につけることができます。 リスト14-2に示したようにですね: ファイル名: src/lib.rs //! # My Crate\n//!\n//! `my_crate` is a collection of utilities to make performing certain\n//! calculations more convenient. //! #自分のクレート\n//!\n//! `my_crate`は、ユーティリティの集まりであり、特定の計算をより便利に行うことができます。 /// Adds one to the number given.\n// --snip-- リスト14-2: 全体としてmy_crateクレートにドキュメントをつける //!で始まる最後の行のあとにコードがないことに注目してください。///ではなく、//!でコメントを開始しているので、 このコメントに続く要素ではなく、このコメントを含む要素にドキュメントをつけているわけです。 今回の場合、このコメントを含む要素は src/lib.rs ファイルであり、クレートのルートです。 これらのコメントは、クレート全体を解説しています。 cargo doc --openを実行すると、これらのコメントは、my_crateのドキュメントの最初のページ、 クレートの公開要素のリストの上部に表示されます。図14-2のようにですね: 図14-2: クレート全体を解説するコメントを含むmy_crateの描画されたドキュメンテーション 要素内のドキュメンテーションコメントは、特にクレートやモジュールを解説するのに有用です。 コンテナの全体の目的を説明し、クレートの使用者がクレートの体系を理解する手助けをするのに使用してください。","breadcrumbs":"CargoとCrates.ioについてより詳しく » 役に立つドキュメンテーションコメントを行う","id":"252","title":"役に立つドキュメンテーションコメントを行う"},"253":{"body":"第7章において、modキーワードを使用してモジュールにコードを体系化する方法、pubキーワードで要素を公開にする方法、 useキーワードで要素をスコープに導入する方法について講義しました。しかしながら、クレートの開発中に、 自分にとって意味のある構造は、ユーザにはあまり便利ではない可能性があります。複数階層を含む階層で、 自分の構造体を体系化したくなるかもしれませんが、それから階層の深いところで定義した型を使用したい人は、 型が存在することを見つけ出すのに困難を伴う可能性もあります。また、そのような人は、 use my_crate::UsefulTypeの代わりにuse my_crate::some_module::another_module::UsefulType;と入力するのを煩わしく感じる可能性もあります。 自分の公開APIの構造は、クレートを公開する際に考慮すべき点です。自分のクレートを使用したい人は、 自分よりもその構造に馴染みがないですし、クレートのモジュール階層が大きければ、使用したい部分を見つけるのが困難になる可能性があります。 嬉しいお知らせは、構造が他人が他のライブラリから使用するのに便利では ない 場合、内部的な体系を再構築する必要はないということです: 代わりに、要素を再エクスポートし、pub useで自分の非公開構造とは異なる公開構造にできます。 再エクスポートは、ある場所の公開要素を一つ取り、別の場所で定義されているかのように別の場所で公開します。 例えば、芸術的な概念をモデル化するためにartという名のライブラリを作ったとしましょう。 このライブラリ内には、2つのモジュールがあります: PrimaryColorとSecondaryColorという名前の2つのenumを含む、 kindsモジュールとmixという関数を含むutilsモジュールです。リスト14-3のようにですね: ファイル名: src/lib.rs //! # Art\n//!\n//! A library for modeling artistic concepts.\n//! #芸術\n//!\n//! 芸術的な概念をモデル化するライブラリ。 pub mod kinds { /// The primary colors according to the RYB color model. /// RYBカラーモデルによる主色 pub enum PrimaryColor { Red, Yellow, Blue, } /// The secondary colors according to the RYB color model. /// RYBカラーモデルによる副色 pub enum SecondaryColor { Orange, Green, Purple, }\n} pub mod utils { use kinds::*; /// Combines two primary colors in equal amounts to create /// a secondary color. ///2つの主色を同じ割合で混合し、副色にする pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor { // --snip-- }\n} リスト14-3: kindsとutilsモジュールに体系化される要素を含むartライブラリ 図14-3は、cargo docにより生成されるこのクレートのドキュメンテーションの最初のページがどんな見た目になるか示しています: 図14-3: kindsとutilsモジュールを列挙するartのドキュメンテーションのトップページ PrimaryColor型もSecondaryColor型も、mix関数もトップページには列挙されていないことに注意してください。 kindsとutilsをクリックしなければ、参照することができません。 このライブラリに依存する別のクレートは、現在定義されているモジュール構造を指定して、 artの要素をインポートするuse文が必要になるでしょう。リスト14-4は、 artクレートからPrimaryColorとmix要素を使用するクレートの例を示しています: ファイル名: src/main.rs extern crate art; use art::kinds::PrimaryColor;\nuse art::utils::mix; fn main() { let red = PrimaryColor::Red; let yellow = PrimaryColor::Yellow; mix(red, yellow);\n} リスト14-4: 内部構造がエクスポートされてartクレートの要素を使用するクレート リスト14-4はartクレートを使用していますが、このコードの筆者は、PrimaryColorがkindsモジュールにあり、 mixがutilsモジュールにあることを理解しなければなりませんでした。artクレートのモジュール構造は、 artクレートの使用者よりも、artクレートに取り組む開発者などに関係が深いです。 クレートの一部をkindsモジュールとutilsモジュールに体系化する内部構造は、artクレートの使用方法を理解しようとする人には、 何も役に立つ情報を含んでいません。代わりに、開発者がどこを見るべきか計算する必要があるので、 artクレートのモジュール構造は混乱を招き、また、開発者はモジュール名をuse文で指定しなければならないので、 この構造は不便です。 公開APIから内部体系を除去するために、リスト14-3のartクレートコードを変更し、pub use文を追加して、 最上位で要素を再エクスポートすることができます。リスト14-5みたいにですね: ファイル名: src/lib.rs //! # Art\n//!\n//! A library for modeling artistic concepts. pub use kinds::PrimaryColor;\npub use kinds::SecondaryColor;\npub use utils::mix; pub mod kinds { // --snip--\n} pub mod utils { // --snip--\n} リスト14-5: pub use文を追加して要素を再エクスポートする このクレートに対してcargo docが生成するAPIドキュメンテーションは、これで図14-4のようにトップページに再エクスポートを列挙しリンクするので、 PrimaryColor型とSecondaryColor型とmix関数を見つけやすくします。 図14-4: 再エクスポートを列挙するartのドキュメンテーションのトップページ artクレートのユーザは、それでも、リスト14-4にデモされているように、リスト14-3の内部構造を見て使用することもできますし、 リスト14-5のより便利な構造を使用することもできます。リスト14-6に示したようにですね: ファイル名: src/main.rs extern crate art; use art::PrimaryColor;\nuse art::mix; fn main() { // --snip--\n} リスト14-6: artクレートの再エクスポートされた要素を使用するプログラム ネストされたモジュールがたくさんあるような場合、最上位階層でpub useにより型を再エクスポートすることは、 クレートの使用者の経験に大きな違いを生みます。 役に立つAPI構造を作ることは、科学というよりも芸術の領域であり、ユーザにとって何が最善のAPIなのか、 探究するために繰り返してみることができます。pub useは、内部的なクレート構造に柔軟性をもたらし、 その内部構造をユーザに提示する構造から切り離してくれます。インストールしてある他のクレートを見て、 内部構造が公開APIと異なっているか確認してみてください。","breadcrumbs":"CargoとCrates.ioについてより詳しく » pub useで便利な公開APIをエクスポートする","id":"253","title":"pub useで便利な公開APIをエクスポートする"},"254":{"body":"クレートを公開する前に、 crates.io のアカウントを作成し、 APIトークンを取得する必要があります。そうするには、 crates.io のホームページを訪れ、 Githubアカウントでログインしてください。(現状は、Githubアカウントがなければなりませんが、 いずれは他の方法でもアカウントを作成できるようになる可能性があります。)ログインしたら、 https://crates.io/me/ で自分のアカウントの設定に行き、 APIキーを取り扱ってください。そして、cargo loginコマンドをAPIキーとともに実行してください。 以下のようにですね: $ cargo login abcdefghijklmnopqrstuvwxyz012345 このコマンドは、CargoにAPIトークンを知らせ、 ~/.cargo/credentials にローカルに保存します。 このトークンは、 秘密 です: 他人とは共有しないでください。なんらかの理由で他人と実際に共有してしまったら、 古いものを破棄して crates.io で新しいトークンを生成するべきです。","breadcrumbs":"CargoとCrates.ioについてより詳しく » Crates.ioのアカウントをセットアップする","id":"254","title":"Crates.ioのアカウントをセットアップする"},"255":{"body":"アカウントはできたので、公開したいクレートがあるとしましょう。公開前に、 Cargo.toml ファイルの[package]セクションに追加することでクレートにメタデータを追加する必要があるでしょう。 クレートには、独自の名前が必要でしょう。クレートをローカルで作成している間、 クレートの名前はなんでもいい状態でした。ところが、 crates.io のクレート名は、 最初に来たもの勝ちの精神で付与されていますので、一旦クレート名が取られてしまったら、 その名前のクレートを他の人が公開することは絶対できません。もう使われているか、 サイトで使いたい名前を検索してください。まだなら、 Cargo.toml ファイルの[package]以下の名前を編集して、 名前を公開用に使ってください。以下のように: ファイル名: Cargo.toml [package]\nname = \"guessing_game\" たとえ、独自の名前を選択していたとしても、この時点でcargo publishを実行すると、警告とエラーが出ます: $ cargo publish Updating registry `https://github.com/rust-lang/crates.io-index`\nwarning: manifest has no description, license, license-file, documentation,\nhomepage or repository.\n(警告: マニフェストに説明、ライセンス、ライセンスファイル、ドキュメンテーション、ホームページ、\nリポジトリがありません)\n--snip--\nerror: api errors: missing or empty metadata fields: description, license.\n(エラー: APIエラー: 存在しないメタデータフィールド: description, license) 原因は、大事な情報を一部入れていないからです: 説明とライセンスは、 他の人があなたのクレートは何をし、どんな条件の元で使っていいのかを知るために必要なのです。 このエラーを解消するには、 Cargo.toml ファイルにこの情報を入れ込む必要があります。 1文か2文程度の説明をつけてください。これは、検索結果に表示されますからね。 licenseフィールドには、 ライセンス識別子 を与える必要があります。 Linux団体のSoftware Package Data Exchange(SPDX) に、この値に使用できる識別子が列挙されています。 例えば、自分のクレートをMITライセンスでライセンスするためには、 MIT識別子を追加してください: ファイル名: Cargo.toml [package]\nname = \"guessing_game\"\nlicense = \"MIT\" SPDXに出現しないライセンスを使用したい場合、そのライセンスをファイルに配置し、 プロジェクトにそのファイルを含め、それからlicenseキーを使う代わりに、 そのファイルの名前を指定するのにlicense-fileを使う必要があります。 どのライセンスが自分のプロジェクトに相(ふ)応(さわ)しいかというガイドは、 この本の範疇を超えています。Rustコミュニティの多くの人間は、MIT OR Apache-2.0のデュアルライセンスを使用することで、 Rust自体と同じようにプロジェクトをライセンスします。この実践は、ORで区切られる複数のライセンス識別子を指定して、 プロジェクトに複数のライセンスを持たせることもできることを模擬しています。 独自の名前、バージョン、クレート作成時にcargo newが追加した筆者の詳細、説明、ライセンスが追加され、 公開準備のできたプロジェクト用のCargo.tomlファイルは以下のような見た目になっていることでしょう: ファイル名: Cargo.toml [package]\nname = \"guessing_game\"\nversion = \"0.1.0\"\nauthors = [\"Your Name \"]\ndescription = \"A fun game where you guess what number the computer has chosen.\" (コンピュータが選択した数字を言い当てる面白いゲーム)\nlicense = \"MIT OR Apache-2.0\" [dependencies] Cargoのドキュメンテーション には、 指定して他人が発見し、より容易くクレートを使用できることを保証する他のメタデータが解説されています。","breadcrumbs":"CargoとCrates.ioについてより詳しく » 新しいクレートにメタデータを追加する","id":"255","title":"新しいクレートにメタデータを追加する"},"256":{"body":"アカウントを作成し、APIトークンを保存し、クレートの名前を決め、必要なメタデータを指定したので、 公開する準備が整いました!クレートを公開すると、特定のバージョンが、 crates.io に他の人が使用できるようにアップロードされます。 公開は 永久 なので、クレートの公開時には気をつけてください。バージョンは絶対に上書きできず、 コードも削除できません。 crates.io の一つの主な目標が、 crates.io のクレートに依存している全てのプロジェクトのビルドが、 動き続けるようにコードの永久アーカイブとして機能することなのです。バージョン削除を可能にしてしまうと、 その目標を達成するのが不可能になってしまいます。ですが、公開できるクレートバージョンの数に制限はありません。 再度cargo publishコマンドを実行してください。今度は成功するはずです: $ cargo publish Updating registry `https://github.com/rust-lang/crates.io-index`\nPackaging guessing_game v0.1.0 (file:///projects/guessing_game)\nVerifying guessing_game v0.1.0 (file:///projects/guessing_game)\nCompiling guessing_game v0.1.0\n(file:///projects/guessing_game/target/package/guessing_game-0.1.0) Finished dev [unoptimized + debuginfo] target(s) in 0.19 secs\nUploading guessing_game v0.1.0 (file:///projects/guessing_game) おめでとうございます!Rustコミュニティとコードを共有し、誰でもあなたのクレートを依存として簡単に追加できます。","breadcrumbs":"CargoとCrates.ioについてより詳しく » Crates.ioに公開する","id":"256","title":"Crates.ioに公開する"},"257":{"body":"クレートに変更を行い、新バージョンをリリースする準備ができたら、 Cargo.toml ファイルに指定されたversionの値を変更し、再公開します。 セマンティックバージョンルール を使用して加えた変更の種類に基づいて次の適切なバージョン番号を決定してください。 そして、cargo publishを実行し、新バージョンをアップロードします。","breadcrumbs":"CargoとCrates.ioについてより詳しく » 既存のクレートの新バージョンを公開する","id":"257","title":"既存のクレートの新バージョンを公開する"},"258":{"body":"以前のバージョンのクレートを削除することはできないものの、将来のプロジェクトがこれに新たに依存することを防ぐことはできます。 これは、なんらかの理由により、クレートバージョンが壊れている場合に有用です。そのような場面において、 Cargoはクレートバージョンの 取り下げ(yank) をサポートしています。 バージョンを取り下げると、既存のプロジェクトは、引き続きダウンロードしたりそのバージョンに依存したりしつづけられますが、 新規プロジェクトが新しくそのバージョンに依存しだすことは防止されます。つまるところ、取り下げは、 すでに Cargo.lock が存在するプロジェクトは壊さないが、将来的に生成された Cargo.lock ファイルは 取り下げられたバージョンを使わない、ということを意味します。 あるバージョンのクレートを取り下げるには、cargo yankを実行し、取り下げたいバージョンを指定します: $ cargo yank --vers 1.0.1 --undoをコマンドに付与することで、取り下げを取り消し、再度あるバージョンにプロジェクトを依存させ始めることもできます: $ cargo yank --vers 1.0.1 --undo 取り下げは、コードの削除は一切し ません 。例として、取り下げ機能は、誤ってアップロードされた秘密鍵を削除するためのものではありません。 もしそうなってしまったら、即座に秘密鍵をリセットしなければなりません。","breadcrumbs":"CargoとCrates.ioについてより詳しく » cargo yankでCrates.ioからバージョンを削除する","id":"258","title":"cargo yankでCrates.ioからバージョンを削除する"},"259":{"body":"第12章で、バイナリクレートとライブラリクレートを含むパッケージを構築しました。プロジェクトの開発が進むにつれて、 ライブラリクレートの肥大化が続き、その上で複数のライブラリクレートにパッケージを分割したくなることでしょう。 この場面において、Cargoは ワークスペース という協調して開発された関連のある複数のパッケージを管理するのに役立つ機能を提供しています。","breadcrumbs":"CargoとCrates.ioについてより詳しく » Cargoのワークスペース","id":"259","title":"Cargoのワークスペース"},"26":{"body":"Cargoを使用して新しいプロジェクトを作成し、元のHello, world!プロジェクトとどう違うかを見ましょう。 projects ディレクトリ(あるいはコードを格納すると決めた場所)に戻ってください。それから、 OSに関わらず、以下を実行してください: $ cargo new hello_cargo --bin\n$ cd hello_cargo 最初のコマンドは、 hello_cargo という新しいバイナリの実行可能ファイルを作成します。cargo newに渡した--bin引数が、 ライブラリとは対照的に実行可能なアプリケーション(よく単に バイナリ と呼ばれる)を作成します。プロジェクトを hello_cargo と名付け、 Cargoは、そのファイルを同名のディレクトリに作成します。 hello_cargo ディレクトリに行き、ファイルを列挙してください。Cargoが2つのファイルと1つのディレクトリを生成してくれたことがわかるでしょう: Cargo.toml ファイルと、中に main.rs ファイルがある src ディレクトリです。また、 .gitignore ファイルと共に、新しいGitリポジトリも初期化しています。 注釈: Gitは一般的なバージョンコントロールシステムです。cargo newを変更して、異なるバージョンコントロールシステムを使用したり、 --vcsフラグを使用して何もバージョンコントロールシステムを使用しないようにもできます。 cargo new --helpを走らせて、利用可能なオプションを確認してください。 お好きなテキストエディタで Cargo.toml を開いてください。リスト1-2のコードのような見た目のはずです。 ファイル名: Cargo.toml [package]\nname = \"hello_cargo\"\nversion = \"0.1.0\"\nauthors = [\"Your Name \"] [dependencies] リスト1-2: cargo newで生成される Cargo.toml の中身 このファイルは TOML ( Tom's Obvious, Minimal Language ; 直訳: トムの明確な最小限の言語)フォーマットで、 Cargoの設定フォーマットです。 最初の行の[package]は、後の文がパッケージを設定していることを示すセクションヘッダーです。もっと情報を追加するにつれて、 別のセクションも追加するでしょう。 その後の3行が、Cargoがプログラムをコンパイルするのに必要な設定情報をセットします: 名前、バージョン、誰が書いたかです。 Cargoは名前とEメールの情報を環境から取得するので、その情報が正しくなければ、 今修正してそれから保存してください。 最後の行の[dependencies]は、プロジェクトの依存を列挙するためのセクションの始まりです。 Rustでは、パッケージのコードは クレート として参照されます。このプロジェクトでは何も他のクレートは必要ありませんが、 第2章の最初のプロジェクトでは必要なので、その時にはこの依存セクションを使用するでしょう。 では、 src/main.rs を開いて覗いてみてください: ファイル名: src/main.rs fn main() { println!(\"Hello, world!\");\n} ちょうどリスト1-1で書いたように、CargoはHello, world!プログラムを生成してくれています。ここまでで、 前のプロジェクトとCargoが生成したプロジェクトの違いは、Cargoが src ディレクトリにコードを配置し、 最上位のディレクトリに Cargo.toml 設定ファイルがあることです。 Cargoは、ソースファイルが src ディレクトリにあることを期待します。プロジェクトの最上位のディレクトリは、 READMEファイル、ライセンス情報、設定ファイル、あるいは、他のコードに関連しないもののためのものです。 Cargoを使用すると、プロジェクトを体系化する手助けをしてくれます。適材適所であり、 全てがその場所にあるのです。 Hello, world!プロジェクトのように、Cargoを使用しないプロジェクトを開始したら、 実際にCargoを使用するプロジェクトに変換することができます。プロジェクトのコードを src ディレクトリに移動し、 適切な Cargo.toml ファイルを作成してください。","breadcrumbs":"事始め » Cargoでプロジェクトを作成する","id":"26","title":"Cargoでプロジェクトを作成する"},"260":{"body":"ワークスペース は、同じ Cargo.lock と出力ディレクトリを共有する一連のパッケージです。 ワークスペースを使用したプロジェクトを作成し、ワークスペースの構造に集中できるよう、瑣末なコードを使用しましょう。 ワークスペースを構築する方法は複数ありますが、一般的な方法を提示しましょう。バイナリ1つとライブラリ2つを含むワークスペースを作ります。 バイナリは、主要な機能を提供しますが、2つのライブラリに依存しています。 一方のライブラリは、add_one関数を提供し、2番目のライブラリは、add_two関数を提供します。 これら3つのクレートが同じワークスペースの一部になります。ワークスペース用の新しいディレクトリを作ることから始めましょう: $ mkdir add\n$ cd add 次に add ディレクトリにワークスペース全体を設定する Cargo.toml ファイルを作成します。 このファイルには、他の Cargo.toml ファイルで見かけるような[package]セクションやメタデータはありません。 代わりにバイナリクレートへのパスを指定することでワークスペースにメンバを追加させてくれる[workspace]セクションから開始します; 今回の場合、そのパスは adder です: ファイル名: Cargo.toml [workspace] members = [ \"adder\",\n] 次に、 add ディレクトリ内でcargo newを実行することでadderバイナリクレートを作成しましょう: $ cargo new --bin adder Created binary (application) `adder` project この時点で、cargo buildを走らせるとワークスペースを構築できます。 add ディレクトリに存在するファイルは、 以下のようになるはずです: ├── Cargo.lock\n├── Cargo.toml\n├── adder\n│ ├── Cargo.toml\n│ └── src\n│ └── main.rs\n└── target ワークスペースには、コンパイルした生成物を置けるように最上位に target のディレクトリがあります; adderクレートには target ディレクトリはありません。 adder ディレクトリ内部からcargo buildを走らせることになっていたとしても、コンパイルされる生成物は、 add/adder/target ではなく、 add/target に落ち着くでしょう。ワークスペースのクレートは、 お互いに依存しあうことを意味するので、Cargoはワークスペースの target ディレクトリをこのように構成します。 各クレートが target ディレクトリを持っていたら、各クレートがワークスペースの他のクレートを再コンパイルし、 target ディレクトリに生成物がある状態にしなければならないでしょう。一つの target ディレクトリを共有することで、 クレートは不必要な再ビルドを回避できるのです。","breadcrumbs":"CargoとCrates.ioについてより詳しく » ワークスペースを生成する","id":"260","title":"ワークスペースを生成する"},"261":{"body":"次に、ワークスペースに別のメンバクレートを作成し、add-oneと呼びましょう。 最上位の Cargo.toml を変更してmembersリストで add-one パスを指定するようにしてください: ファイル名: Cargo.toml [workspace] members = [ \"adder\", \"add-one\",\n] それから、add-oneという名前のライブラリクレートを生成してください: $ cargo new add-one --lib Created library `add-one` project これで add ディレクトリには、以下のディレクトリやファイルが存在するはずです: ├── Cargo.lock\n├── Cargo.toml\n├── add-one\n│ ├── Cargo.toml\n│ └── src\n│ └── lib.rs\n├── adder\n│ ├── Cargo.toml\n│ └── src\n│ └── main.rs\n└── target add-one/src/lib.rs ファイルにadd_one関数を追加しましょう: ファイル名: add-one/src/lib.rs pub fn add_one(x: i32) -> i32 { x + 1\n} ワークスペースにライブラリクレートが存在するようになったので、バイナリクレートadderをライブラリクレートのadd-oneに依存させられます。 まず、add-oneへのパス依存を adder/Cargo.toml に追加する必要があります: ファイル名: adder/Cargo.toml [dependencies] add-one = { path = \"../add-one\" } Cargoはワークスペースのクレートが、お互いに依存しているとは想定していないので、 クレート間の依存関係について明示する必要があります。 次に、adderクレートのadd-oneクレートからadd_one関数を使用しましょう。 adder/src/main.rs ファイルを開き、 冒頭にextern crate行を追加して新しいadd-oneライブラリクレートをスコープに導入してください。 それからmain関数を変更し、add_one関数を呼び出します。リスト14-7のようにですね: ファイル名: adder/src/main.rs extern crate add_one; fn main() { let num = 10; // こんにちは世界!{}+1は{}! println!(\"Hello, world! {} plus one is {}!\", num, add_one::add_one(num));\n} リスト14-7: adderクレートからadd-oneライブラリクレートを使用する 最上位の add ディレクトリでcargo buildを実行することでワークスペースをビルドしましょう! $ cargo build Compiling add-one v0.1.0 (file:///projects/add/add-one) Compiling adder v0.1.0 (file:///projects/add/adder) Finished dev [unoptimized + debuginfo] target(s) in 0.68 secs add ディレクトリからバイナリクレートを実行するには、-p引数とパッケージ名をcargo runと共に使用して、 使用したいワークスペースのパッケージを指定する必要があります: $ cargo run -p adder Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs Running `target/debug/adder`\nHello, world! 10 plus one is 11! これにより、 adder/src/main.rs のコードが実行され、これはadd_oneクレートに依存しています。 ワークスペースの外部クレートに依存する ワークスペースには、各クレートのディレクトリそれぞれに Cargo.lock が存在するのではなく、 ワークスペースの最上位階層にただ一つの Cargo.lock が存在するだけのことに注目してください。 これにより、全クレートが全依存の同じバージョンを使用していることが確認されます。 randクレートを adder/Cargo.toml と add-one/Cargo.toml ファイルに追加すると、 Cargoは両者をあるバージョンのrandに解決し、それを一つの Cargo.lock に記録します。 ワークスペースの全クレートに同じ依存を使用させるということは、 ワークスペースのクレートが相互に互換性を常に維持するということになります。 add-one/Cargo.toml ファイルの[dependencies]セクションにrandクレートを追加して、 add-oneクレートでrandクレートを使用できます: ファイル名: add-one/Cargo.toml [dependencies] rand = \"0.3.14\" これで、 add-one/src/lib.rs ファイルにextern crate rand;を追加でき、 add ディレクトリでcargo buildを実行することでワークスペース全体をビルドすると、 randクレートを持ってきてコンパイルするでしょう: $ cargo build Updating registry `https://github.com/rust-lang/crates.io-index` Downloading rand v0.3.14 --snip-- Compiling rand v0.3.14 Compiling add-one v0.1.0 (file:///projects/add/add-one) Compiling adder v0.1.0 (file:///projects/add/adder) Finished dev [unoptimized + debuginfo] target(s) in 10.18 secs さて、最上位の Cargo.lock は、randに対するadd-oneの依存の情報を含むようになりました。 ですが、randはワークスペースのどこかで使用されているにも関わらず、それぞれの Cargo.toml ファイルにも、 randを追加しない限り、ワークスペースの他のクレートでそれを使用することはできません。 例えば、adderクレートの adder/src/main.rs ファイルにextern crate rand;を追加すると、 エラーが出ます: $ cargo build Compiling adder v0.1.0 (file:///projects/add/adder)\nerror: use of unstable library feature 'rand': use `rand` from crates.io (see\nissue #27703)\n(エラー: 不安定なライブラリの機能'rand'を使用しています: crates.ioの`rand`を使用してください) --> adder/src/main.rs:1:1 |\n1 | extern crate rand; これを修正するには、adderクレートの Cargo.toml ファイルを編集し、同様にそのクレートがrandに依存していることを示してください。 adderクレートをビルドすると、randを Cargo.lock のadderの依存一覧に追加しますが、 randのファイルが追加でダウンロードされることはありません。Cargoが、ワークスペースのrandを使用するどのクレートも、 同じバージョンを使っていることを確かめてくれるのです。ワークスペース全体でrandの同じバージョンを使用することにより、 複数のコピーが存在しないのでスペースを節約し、ワークスペースのクレートが相互に互換性を維持することを確かめます。 ワークスペースにテストを追加する さらなる改善として、add_oneクレート内にadd_one::add_one関数のテストを追加しましょう: ファイル名: add-one/src/lib.rs pub fn add_one(x: i32) -> i32 { x + 1\n} #[cfg(test)]\nmod tests { use super::*; #[test] fn it_works() { assert_eq!(3, add_one(2)); }\n} では、最上位の add ディレクトリでcargo testを実行してください: $ cargo test Compiling add-one v0.1.0 (file:///projects/add/add-one) Compiling adder v0.1.0 (file:///projects/add/adder) Finished dev [unoptimized + debuginfo] target(s) in 0.27 secs Running target/debug/deps/add_one-f0253159197f7841 running 1 test\ntest tests::it_works ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out Running target/debug/deps/adder-f88af9d2cc175a5e running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out Doc-tests add-one running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out 出力の最初の区域が、add-oneクレートのit_worksテストが通ったことを示しています。 次の区域には、adderクレートにはテストが見つからなかったことが示され、 さらに最後の区域には、add-oneクレートにドキュメンテーションテストは見つからなかったと表示されています。 このような構造をしたワークスペースでcargo testを走らせると、ワークスペースの全クレートのテストを実行します。 -pフラグを使用し、テストしたいクレートの名前を指定することで最上位ディレクトリから、 ワークスペースのある特定のクレート用のテストを実行することもできます: $ cargo test -p add-one Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs Running target/debug/deps/add_one-b3235fea9a156f74 running 1 test\ntest tests::it_works ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out Doc-tests add-one running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out この出力は、cargo testがadd-oneクレートのテストのみを実行し、adderクレートのテストは実行しなかったことを示しています。 ワークスペースのクレートを https://crates.io/ に公開したら、ワークスペースのクレートは個別に公開される必要があります。 cargo publishコマンドには--allフラグや-pフラグはないので、各クレートのディレクトリに移動して、 ワークスペースの各クレートをcargo publishして、公開しなければなりません。 鍛錬を積むために、add-oneクレートと同様の方法でワークスペースにadd-twoクレートを追加してください! プロジェクトが肥大化してきたら、ワークスペースの使用を考えてみてください: 大きな一つのコードの塊よりも、 微細で個別のコンポーネントの方が理解しやすいです。またワークスペースにクレートを保持することは、 同時に変更されることが多いのなら、協調しやすくなることにも繋がります。","breadcrumbs":"CargoとCrates.ioについてより詳しく » ワークスペース内に2番目のクレートを作成する","id":"261","title":"ワークスペース内に2番目のクレートを作成する"},"262":{"body":"cargo installコマンドにより、バイナリクレートをローカルにインストールし、使用することができます。 これは、システムパッケージを置き換えることを意図したものではありません。即(すなわ)ち、 Rustの開発者が、他人が crates.io に共有したツールをインストールするのに便利な方法を意味するのです。 バイナリターゲットを持つパッケージのみインストールできることに注意してください。バイナリターゲットとは、 クレートが src/main.rs ファイルやバイナリとして指定された他のファイルを持つ場合に生成される実行可能なプログラムのことであり、 単独では実行不可能なものの、他のプログラムに含むのには適しているライブラリターゲットとは一線を画します。 通常、クレートには、 README ファイルに、クレートがライブラリかバイナリターゲットか、両方をもつかという情報があります。 cargo installでインストールされるバイナリは全て、インストールのルートの bin フォルダに保持されます。 Rustをrustupを使用し、独自の設定を何も行なっていなければ、このディレクトリは、 $HOME/.cargo/bin になります。 cargo installでインストールしたプログラムを実行できるようにするためには、そのディレクトリが$PATHに含まれていることを確かめてください。 例えば、第12章で、ファイルを検索するripgrepというgrepツールのRust版があることに触れました。 ripgrepをインストールしたかったら、以下を実行することができます: $ cargo install ripgrep\nUpdating registry `https://github.com/rust-lang/crates.io-index` Downloading ripgrep v0.3.2 --snip-- Compiling ripgrep v0.3.2 Finished release [optimized + debuginfo] target(s) in 97.91 secs Installing ~/.cargo/bin/rg 出力の最後の行が、インストールされたバイナリの位置と名前を示していて、ripgrepの場合、rgです。 インストールディレクトリが$PATHに存在する限り、前述したように、rg --helpを走らせて、 より高速でRustらしいファイル検索ツールを使用し始めることができます!","breadcrumbs":"CargoとCrates.ioについてより詳しく » cargo installでCrates.ioからバイナリをインストールする","id":"262","title":"cargo installでCrates.ioからバイナリをインストールする"},"263":{"body":"Cargoは変更する必要なく、新しいサブコマンドで拡張できるように設計されています。 $PATHにあるバイナリがcargo-somethingという名前なら、cargo somethingを実行することで、 Cargoのサブコマンドであるかのように実行することができます。このような独自のコマンドは、 cargo --listを実行すると、列挙もされます。cargo installを使用して拡張をインストールし、 それから組み込みのCargoツール同様に実行できることは、Cargoの設計上の非常に便利な恩恵です!","breadcrumbs":"CargoとCrates.ioについてより詳しく » 独自のコマンドでCargoを拡張する","id":"263","title":"独自のコマンドでCargoを拡張する"},"264":{"body":"Cargoで crates.io とコードを共有することは、 Rustのエコシステムを多くの異なる作業に有用にするものの一部です。Rustの標準ライブラリは、 小さく安定的ですが、クレートは共有および使用しやすく、言語とは異なるタイムラインで進化します。 積極的に crates.io で自分にとって有用なコードを共有してください; 他の誰かにとっても、役に立つものであることでしょう!","breadcrumbs":"CargoとCrates.ioについてより詳しく » まとめ","id":"264","title":"まとめ"},"265":{"body":"ポインタ は、メモリのアドレスを含む変数の一般的な概念です。このアドレスは、何らかの他のデータを参照、または「指します」。 Rustにおいて、最もありふれた種類のポインタは、参照であり、第4章で習いましたね。参照は、 &記号で示唆され、指している値を借用します。データを参照すること以外に特別な能力は何もありません。 また、オーバーヘッドもなく、最も頻繁に使われる種類のポインタです。 一方、 スマートポインタ は、ポインタのように振る舞うだけでなく、追加のメタデータと能力があるデータ構造です。 スマートポインタという概念は、Rustに特有のものではありません: スマートポインタは、C++に端を発し、 他の言語にも存在しています。Rustでは、標準ライブラリに定義された色々なスマートポインタが、 参照以上の機能を提供します。この章で探究する一つの例が、 参照カウント 方式のスマートポインタ型です。 このポインタにより、所有者の数を追いかけることでデータに複数の所有者を持たせることができ、 所有者がいなくなったら、データの片付けをしてくれます。 所有権と借用の概念を使うRustで、参照とスマートポインタの別の差異は、参照はデータを借用するだけのポインタであることです; 対照的に多くの場合、スマートポインタは指しているデータを 所有 します。 その時は、スマートポインタとは呼ばなかったものの、第8章のStringやVecのように、 この本の中でいくつかのスマートポインタに遭遇してきました。これらの型はどちらも、 あるメモリを所有し、それを弄ることができるので、スマートポインタに数えられます。また、 メタデータ(キャパシティなど)や追加の能力、あるいは保証(Stringならデータが常に有効なUTF-8であると保証することなど)もあります。 スマートポインタは普通、構造体を使用して実装されています。スマートポインタを通常の構造体と区別する特徴は、 スマートポインタは、DerefとDropトレイトを実装していることです。Derefトレイトにより、スマートポインタ構造体のインスタンスは、 参照のように振る舞うことができるので、参照あるいはスマートポインタのどちらとも動作するコードを書くことができます。 Dropトレイトにより、スマートポインタのインスタンスがスコープを外れた時に走るコードをカスタマイズすることができます。 この章では、どちらのトレイトについても議論し、これらのトレイトがスマートポインタにとって重要な理由を説明します。 スマートポインタパターンがRustにおいてよく使われる一般的なデザインパターンだとして、この章では、全ての既存のスマートポインタを講義しません。 多くのライブラリに独自のスマートポインタがあり、自分だけのスマートポインタを書くことさえできます。 標準ライブラリの最もありふれたスマートポインタを講義します: ヒープに値を確保するBox 複数の所有権を可能にする参照カウント型のRc RefCellを通してアクセスされ、コンパイル時ではなく実行時に借用規則を強制する型のRefとRefMut さらに、不変な型が、内部の値を可変化するAPIを晒す 内部可変性 パターンについても講義します。 また、 循環参照 についても議論します: 循環参照により、メモリがリークする方法とそれを回避する方法です。 さあ、飛び込みましょう!","breadcrumbs":"スマートポインタ","id":"265","title":"スマートポインタ"},"266":{"body":"最も素直なスマートポインタは ボックス であり、その型はBoxと記述されます。 ボックスにより、スタックではなくヒープにデータを格納することができます。スタックに残るのは、 ヒープデータへのポインタです。スタックとヒープの違いを再確認するには、第4章を参照されたし。 ボックスは、データをスタックの代わりにヒープに格納する以外は、パフォーマンスのオーバーヘッドはありません。 しかし、多くのおまけの能力もありません。以下のような場面で最もよく使用するでしょう: コンパイル時にはサイズを知ることができない型があり、正確なサイズを要求する文脈でその型の値を使用する時 多くのデータがあり、所有権を転送したいが、その際にデータがコピーされないようにしたい時 値を所有する必要があり、特定の型ではなく特定のトレイトを実装する型であることのみ気にかけている時 「ボックスで再帰的な型を可能にする」節で1つ目の場合について実際に説明します。 2番目の場合、多くのデータの所有権を転送するには、データがスタック上でコピーされるので、長い時間がかかり得ます。 この場面でパフォーマンスを向上させるには、多くのデータをヒープ上にボックスとして格納することができます。 そして、参照しているデータはヒープ上の1箇所に留まりつつ、少量のポインタのデータのみをスタック上でコピーするのです。 3番目のケースは、 トレイトオブジェクト として知られ、第17章の「トレイトオブジェクトで異なる型の値を許容する」の節は、 すべてその話題を説明するためだけのものです。 従って、ここで学ぶのと同じことが第17章においても適用するでしょう!","breadcrumbs":"スマートポインタ » ヒープのデータを指すBoxを使用する","id":"266","title":"ヒープのデータを指すBoxを使用する"},"267":{"body":"Boxのこのユースケースを議論する前に、Boxの記法と、Box内に格納された値を読み書きする方法について講義しましょう。 リスト15-1は、ボックスを使用してヒープにi32の値を格納する方法を示しています: ファイル名: src/main.rs fn main() { let b = Box::new(5); println!(\"b = {}\", b);\n} リスト15-1: ボックスを使用してi32の値をヒープに格納する 変数bを定義して値5を指すBoxの値があって、この値はヒープに確保されています。このプログラムは、 b = 5と出力するでしょう; この場合、このデータがスタックにあるのと同じような方法でボックスのデータにアクセスできます。 あらゆる所有された値同様、bがmainの終わりでするようにボックスがスコープを抜けたら、 メモリから解放されます。メモリの解放は(スタックに格納されている)ボックスと(ヒープに格納されている)指しているデータに対して起きます。 ヒープに単独の値を置くことはあまり有用ではないので、このように単独でボックスを使用することはあまりありません。 単独のi32のような値は、既定で格納される場所であるスタックに置くことが、大多数の場合にはより適切です。 ボックスがなかったら定義することの叶わない型をボックスが定義させてくれる場合を見ましょう。","breadcrumbs":"スマートポインタ » Boxを使ってヒープにデータを格納する","id":"267","title":"Boxを使ってヒープにデータを格納する"},"268":{"body":"コンパイル時に、コンパイラは、ある型が取る領域を知る必要があります。コンパイル時にサイズがわからない型の1つは、 再帰的な型 であり、これは、型の一部として同じ型の他の値を持つものです。この値のネストは、 理論的には無限に続く可能性があるので、コンパイラは再帰的な型の値が必要とする領域を知ることができないのです。 しかしながら、ボックスは既知のサイズなので、再帰的な型の定義にボックスを挟むことで再帰的な型を存在させることができるのです。 コンスリスト は関数型プログラミング言語では一般的なデータ型ですが、これを再帰的な型の例として探究しましょう。 我々が定義するコンスリストは、再帰を除いて素直です; 故に、これから取り掛かる例の概念は、 再帰的な型が関わるもっと複雑な場面に遭遇したら必ず役に立つでしょう。 コンスリストについてもっと詳しく コンスリストは、Lispプログラミング言語とその方言に由来するデータ構造です。Lispでは、 cons関数(\"construct function\"の省略形です)が2つの引数から新しいペアを構成し、 この引数は通常、単独の値と別のペアからなります。これらのペアを含むペアがリストをなすのです。 cons関数の概念は、より一般的な関数型プログラミングの俗語にもなっています: \"to cons x onto y \"は、 俗に要素 x をこの新しいコンテナの初めに置き、コンテナ y を続けて新しいコンテナのインスタンスを生成することを意味します。 コンスリストの各要素は、2つの要素を含みます: 現在の要素の値と次の要素です。リストの最後の要素は、 次の要素なしにNilと呼ばれる値だけを含みます。コンスリストは、繰り返しcons関数を呼び出すことで生成されます。 繰り返しの規範事例を意味する標準的な名前は、Nilです。これは第6章の\"null\"や\"nil\"の概念とは異なることに注意してください。 \"null\"や\"nil\"は、無効だったり存在しない値です。 関数型プログラミング言語は、頻繁にコンスリストを使用するものの、Rustではあまり使用されないデータ構造です。 Rustで要素のリストがある場合はほとんどの場合、Vecを使用するのがよりよい選択になります。 他のより複雑で再帰的なデータ型は、様々な場面で役に立ち ます が、コンスリストから始めることで、 大して気を散らすことなく再帰的なデータ型をボックスが定義させてくれる方法を探究することができます。 リスト15-2には、コンスリストのenum定義が含まれています。このコードは、 List型が既知のサイズではないため、まだコンパイルできないことに注意してください。 既知のサイズがないことをこれから模擬します。 ファイル名: src/main.rs enum List { Cons(i32, List), Nil,\n} リスト15-2: i32値のコンスリストデータ構造を表すenumを定義する最初の試行 注釈: この例のためだけにi32値だけを保持するコンスリストを実装します。第10章で議論したように、 ジェネリクスを使用してどんな型の値も格納できるコンスリストを定義して実装することもできたでしょう。 このList型を使用してリスト1, 2, 3を格納すると、リスト15-3のコードのような見た目になるでしょう: ファイル名: src/main.rs use List::{Cons, Nil}; fn main() { let list = Cons(1, Cons(2, Cons(3, Nil)));\n} リスト15-3: List enumを使用してリスト1, 2, 3を格納する 最初のCons値は、1と別のList値を保持しています。このList値は、 2とまた別のList値を保持する別のCons値です。このList値は、 3と、ついにリストの終端を通知する非再帰的な列挙子のNilになるList値を保持するまたまた別のCons値です。 リスト15-3のコードをコンパイルしようとすると、リスト15-4に示したエラーが出ます: error[E0072]: recursive type `List` has infinite size\n(エラー: 再帰的な型`List`は無限のサイズです) --> src/main.rs:1:1 |\n1 | enum List { | ^^^^^^^^^ recursive type has infinite size\n2 | Cons(i32, List), | ----- recursive without indirection | = help: insert indirection (e.g., a `Box`, `Rc`, or `&`) at some point to make `List` representable (助言: 間接参照(例: `Box`、`Rc`、あるいは`&`)をどこかに挿入して、`List`を表現可能にしてください) リスト15-4: 再帰的なenumを定義しようとすると得られるエラー エラーは、この型は「無限のサイズである」と表示しています。理由は、再帰的な列挙子を含むListを定義したからです: 自身の別の値を直接保持しているのです。結果として、コンパイラは、List値を格納するのに必要な領域が計算できないのです。 このエラーが得られた理由を少し噛み砕きましょう。まず、非再帰的な型の値を格納するのに必要な領域をどうコンパイラが決定しているかを見ましょう。 非再帰的な型のサイズを計算する 第6章でenum定義を議論した時にリスト6-2で定義したMessage enumを思い出してください: enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32),\n} Message値一つにメモリを確保するために必要な領域を決定するために、コンパイラは、 各列挙子を見てどの列挙子が最も領域を必要とするかを確認します。コンパイラは、 Message::Quitは全く領域を必要とせず、Message::Moveはi32値を2つ格納するのに十分な領域が必要などと確かめます。 ただ1つの列挙子しか使用されないので、Message値一つが必要とする最大の領域は、 最大の列挙子を格納するのに必要になる領域です。 これをコンパイラがリスト15-2のList enumのような再帰的な型が必要とする領域を決定しようとする時に起こることと比較してください。 コンパイラは、Cons列挙子を見ることから始め、この列挙子には、型i32値が一つと型Listの値が一つ保持されます。 故に、Consは1つのi32とListのサイズに等しい領域を必要とします。Listが必要とするメモリ量を計算するのに、 コンパイラはCons列挙子から列挙子を観察します。Cons列挙子は型i32を1つと型Listの値1つを保持し、 この過程は無限に続きます。図15-1のようにですね。 図15-1: 無限のCons列挙子からなる無限のList Boxで既知のサイズの再帰的な型を得る コンパイラは、再帰的に定義された型に必要なメモリ量を計算できないので、リスト15-4ではエラーを返します。 しかし、エラーには確かにこの役に立つ提言が含まれています: = help: insert indirection (e.g., a `Box`, `Rc`, or `&`) at some point to make `List` representable この提言において、「間接参照」は、値を直接格納する代わりに、データ構造を変更して値へのポインタを代わりに格納することで、 値を間接的に格納することを意味します。 Boxはポインタなので、コンパイラにはBoxが必要とする領域が必ずわかります: ポインタのサイズは、 指しているデータの量によって変わることはありません。つまり、別のList値を直接置く代わりに、 Cons列挙子の中にBoxを配置することができます。Boxは、 Cons列挙子の中ではなく、ヒープに置かれる次のList値を指します。概念的には、 それでも他のリストを「保持する」リストとともに作られたリストがありますが、 この実装は今では、要素はお互いの中にあるというよりも、隣り合って配置するような感じになります。 リスト15-2のList enumの定義とリスト15-3のListの使用をリスト15-5のコードに変更することができ、 これはコンパイルが通ります: ファイル名: src/main.rs enum List { Cons(i32, Box), Nil,\n} use List::{Cons, Nil}; fn main() { let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));\n} リスト15-5: 既知のサイズにするためにBoxを使用するListの定義 Cons列挙子は、1つのi32のサイズに加えてボックスのポインタデータを格納する領域を必要とするでしょう。 Nil列挙子は、値を格納しないので、Cons列挙子よりも必要な領域は小さいです。これで、 どんなList値もi321つのサイズに加えてボックスのポインタデータのサイズを必要とすることがわかりました。 ボックスを使うことで、無限の再帰的な繰り返しを破壊したので、コンパイラは、List値を格納するのに必要なサイズを計算できます。 図15-2は、Cons列挙子の今の見た目を示しています。 図15-2: ConsがBoxを保持しているので、無限にサイズがあるわけではないList ボックスは、間接参照とヒープメモリ確保だけを提供します; 他のスマートポインタ型で目撃するような、 他の特別な能力は何もありません。これらの特別な能力が招くパフォーマンスのオーバーヘッドもないので、 間接参照だけが必要になる唯一の機能であるコンスリストのような場合に有用になり得ます。 より多くのボックスのユースケースは第17章でもお見かけするでしょう。 Box型は、Derefトレイトを実装しているので、スマートポインタであり、 このトレイトによりBoxの値を参照のように扱うことができます。Box値がスコープを抜けると、 Dropトレイト実装によりボックスが参照しているヒープデータも片付けられます。 これら2つのトレイトをより詳しく探究しましょう。これら2つのトレイトは、 この章の残りで議論する他のスマートポインタ型で提供される機能にとってさらに重要でしょう。","breadcrumbs":"スマートポインタ » ボックスで再帰的な型を可能にする","id":"268","title":"ボックスで再帰的な型を可能にする"},"269":{"body":"Derefトレイトを実装することで 参照外し演算子 の*(掛け算やグロブ演算子とは違います)の振る舞いをカスタマイズすることができます。 スマートポインタを普通の参照のように扱えるようにDerefを実装することで、 参照に対して処理を行うコードを書き、そのコードをスマートポインタとともにも使用できます。 まずは、参照外し演算子が普通の参照に対して動作するところを見ましょう。それからBoxのように振る舞う独自の型を定義し、 参照外し演算子が新しく定義した型に対して参照のように動作しない理由を確認しましょう。 Derefトレイトを実装することでスマートポインタが参照と似た方法で動作するようにできる方法を探求します。 そして、Rustの 参照外し型強制 機能と、それにより参照やスマートポインタと協調できる方法を見ます。","breadcrumbs":"スマートポインタ » Derefトレイトでスマートポインタを普通の参照のように扱う","id":"269","title":"Derefトレイトでスマートポインタを普通の参照のように扱う"},"27":{"body":"さて、CargoでHello, world!プログラムをビルドし、実行する時の違いに目を向けましょう! hello_cargo ディレクトリから、 以下のコマンドを入力してプロジェクトをビルドしてください: $ cargo build Compiling hello_cargo v0.1.0 (file:///projects/hello_cargo) Finished dev [unoptimized + debuginfo] target(s) in 2.85 secs このコマンドは、カレントディレクトリではなく、 target/debug/hello_cargo (あるいはWindowsなら、 target/debug/hello_cargo.exe )に実行可能ファイルを作成します。以下のコマンドで実行可能ファイルを実行できます: $ ./target/debug/hello_cargo # or .\\target\\debug\\hello_cargo.exe on Windows # あるいは、Windowsなら、.\\target\\debug\\hello_cargo.exe\nHello, world! 全てがうまくいけば、Hello, world!が端末に出力されるはずです。初めてcargo buildを実行すると、 Cargoが最上位に新しいファイルも作成します: Cargo.lock です。このファイルは、自分のプロジェクトの依存の正確なバージョンを追いかけます。 このプロジェクトには依存がないので、ファイルはやや空っぽです。絶対にこのファイルを手動で変更する必要はないでしょう; Cargoが中身を管理してくれるのです。 cargo buildでプロジェクトをビルドし、./target/debug/hello_cargoで実行したばかりですが、 cargo runを使用して、コードをコンパイルし、それから吐かれた実行可能ファイルを全部1コマンドで実行することもできます: $ cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs Running `target/debug/hello_cargo`\nHello, world! 今回は、Cargoがhello_cargoをコンパイルしていることを示唆する出力がないことに注目してください。 Cargoはファイルが変更されていないことを推察したので、単純にバイナリを実行したのです。 ソースコードを変更していたら、Cargoは実行前にプロジェクトを再ビルドし、こんな出力を目の当たりにしたでしょう: $ cargo run Compiling hello_cargo v0.1.0 (file:///projects/hello_cargo) Finished dev [unoptimized + debuginfo] target(s) in 0.33 secs Running `target/debug/hello_cargo`\nHello, world! Cargoはcargo checkというコマンドも提供しています。このコマンドは、迅速にコードを確認し、 コンパイルできることを確かめますが、実行可能ファイルは生成しません: $ cargo check Compiling hello_cargo v0.1.0 (file:///projects/hello_cargo) Finished dev [unoptimized + debuginfo] target(s) in 0.32 secs 何故、実行可能ファイルが欲しくないのでしょうか?しばしば、cargo checkは、cargo buildよりも遥かに速くなります。 実行可能ファイルを生成する手順を飛ばすからです。コードを書いている際に継続的に自分の作業を確認するのなら、 cargo checkを使用すると、その過程が高速化されます!そのため、多くのRustaceanは、 プログラムを書く際にコンパイルできるか確かめるために定期的にcargo checkを実行します。 そして、実行可能ファイルを使用できる状態になったら、cargo buildを走らせるのです。 ここまでにCargoについて学んだことをおさらいしましょう: cargo buildかcargo checkでプロジェクトをビルドできる。 プロジェクトのビルドと実行を1ステップ、cargo runでできる。 ビルドの結果をコードと同じディレクトリに保存するのではなく、Cargoは target/debug ディレクトリに格納する。 Cargoを使用する追加の利点は、使用しているOSに関わらず、同じコマンドが使用できることです。 故にこの時点で、WindowsとLinux及びmacOSで特定の手順を提供することは最早なくなります。","breadcrumbs":"事始め » Cargoプロジェクトをビルドし、実行する","id":"27","title":"Cargoプロジェクトをビルドし、実行する"},"270":{"body":"普通の参照は1種のポインタであり、ポインタの捉え方の一つが、どこか他の場所に格納された値への矢印としてです。 リスト15-6で、i32値への参照を生成し、それから参照外し演算子を使用して参照をデータまで追いかけています: ファイル名: src/main.rs fn main() { let x = 5; let y = &x; assert_eq!(5, x); assert_eq!(5, *y);\n} リスト15-6: 参照外し演算子を使用して参照をi32値まで追いかける 変数xはi32値の5を保持しています。yをxへの参照にセットします。xは5に等しいとアサートできます。 しかしながら、yの値に関するアサートを行いたい場合、*yを使用して参照を指している値まで追いかけなければなりません(そのため 参照外し です)。 一旦、yを参照外ししたら、yが指している5と比較できる整数値にアクセスできます。 代わりにassert_eq!(5, y);と書こうとしたら、こんなコンパイルエラーが出るでしょう: error[E0277]: the trait bound `{integer}: std::cmp::PartialEq<&{integer}>` is\nnot satisfied\n(エラー: トレイト境界`{integer}: std::cmp::PartialEq<&{integer}>`は満たされていません) --> src/main.rs:6:5 |\n6 | assert_eq!(5, y); | ^^^^^^^^^^^^^^^^^ can't compare `{integer}` with `&{integer}` | = help: the trait `std::cmp::PartialEq<&{integer}>` is not implemented for `{integer}` (助言: トレイト`std::cmp::PartialEq<&{integer}>`は`{integer}`に対して実装されていません) 参照と数値は異なる型なので、比較することは許容されていません。参照外し演算子を使用して、 参照を指している値まで追いかけなければならないのです。","breadcrumbs":"スマートポインタ » 参照外し演算子で値までポインタを追いかける","id":"270","title":"参照外し演算子で値までポインタを追いかける"},"271":{"body":"リスト15-6のコードを参照の代わりにBoxを使うように書き直すことができます; 参照外し演算子は、リスト15-7に示したように動くでしょう: ファイル名: src/main.rs fn main() { let x = 5; let y = Box::new(x); assert_eq!(5, x); assert_eq!(5, *y);\n} リスト15-7: Boxに対して参照外し演算子を使用する リスト15-7とリスト15-6の唯一の違いは、ここでは、xの値を指す参照ではなく、 xの値を指すボックスのインスタンスにyをセットしていることです。 最後のアサートで参照外し演算子を使用してyが参照だった時のようにボックスのポインタを追いかけることができます。 次に、独自のボックス型を定義することで参照外し演算子を使用させてくれるBoxについて何が特別なのかを探究します。","breadcrumbs":"スマートポインタ » Boxを参照のように使う","id":"271","title":"Boxを参照のように使う"},"272":{"body":"標準ライブラリが提供しているBox型に似たスマートポインタを構築して、スマートポインタは既定で 参照に比べてどう異なって振る舞うのか経験しましょう。それから、参照外し演算子を使う能力を追加する方法に目を向けましょう。 Box型は究極的に1要素のタプル構造体として定義されているので、リスト15-8は、同じようにMyBox型を定義しています。 また、Boxに定義されたnew関数と合致するnew関数も定義しています。 ファイル名: src/main.rs struct MyBox(T); impl MyBox { fn new(x: T) -> MyBox { MyBox(x) }\n} リスト15-8: MyBox型を定義する MyBoxという構造体を定義し、ジェネリック引数のTを宣言しています。自分の型にどんな型の値も保持させたいからです。 MyBox型は、型Tを1要素持つタプル構造体です。MyBox::new関数は型Tの引数を1つ取り、 渡した値を保持するMyBoxインスタンスを返します。 試しにリスト15-7のmain関数をリスト15-8に追加し、Boxの代わりに定義したMyBox型を使うよう変更してみてください。 コンパイラはMyBoxを参照外しする方法がわからないので、リスト15-9のコードはコンパイルできません。 ファイル名: src/main.rs fn main() { let x = 5; let y = MyBox::new(x); assert_eq!(5, x); assert_eq!(5, *y);\n} リスト15-9: 参照とBoxを使ったのと同じようにMyBoxを使おうとする こちらが結果として出るコンパイルエラーです: error[E0614]: type `MyBox<{integer}>` cannot be dereferenced\n(エラー: 型`MyBox<{integer}>`は参照外しできません) --> src/main.rs:14:19 |\n14 | assert_eq!(5, *y); | ^^ MyBoxに参照外しの能力を実装していないので、参照外しできません。*演算子で参照外しできるようにするには、 Derefトレイトを実装します。","breadcrumbs":"スマートポインタ » 独自のスマートポインタを定義する","id":"272","title":"独自のスマートポインタを定義する"},"273":{"body":"第10章で議論したように、トレイトを実装するには、トレイトの必須メソッドに実装を提供する必要があります。 Derefトレイトは標準ライブラリで提供されていますが、selfを借用し、 内部のデータへの参照を返すderefという1つのメソッドを実装する必要があります。リスト15-10には、 MyBoxの定義に追記するDerefの実装が含まれています: ファイル名: src/main.rs use std::ops::Deref; # struct MyBox(T);\nimpl Deref for MyBox { type Target = T; fn deref(&self) -> &T { &self.0 }\n} リスト15-10: MyBoxにDerefを実装する type Target = T;という記法は、Derefトレイトが使用する関連型を定義しています。関連型は、 ジェネリック引数を宣言する少しだけ異なる方法ですが、今は気にする必要はありません; 第19章でより詳しく講義します。 derefメソッドの本体を&self.0で埋めているので、derefは*演算子でアクセスしたい値への参照を返します。 リスト15-9のMyBoxに*を呼び出すmain関数はこれでコンパイルでき、アサートも通ります! Derefがなければ、コンパイラは&参照しか参照外しできなくなります。derefメソッドによりコンパイラは、 Derefを実装するあらゆる型の値を取り、derefメソッドを呼び出して参照外しの仕方を知っている&参照を得る能力を獲得するのです。 リスト15-9に*yを入力した時、水面下でコンパイラは、実際にはこのようなコードを走らせていました: *(y.deref()) コンパイラは、*演算子をderefメソッド、それから何の変哲もない参照外しの呼び出しに置き換えるので、 derefメソッドを呼び出す必要があるかどうかを考える必要はないわけです。このRustの機能により、 普通の参照かDerefを実装した型であるかどうかに関わらず、等しく機能するコードを書かせてくれます。 derefメソッドが値への参照を返し、*(y.deref())のかっこの外の何の変哲もない参照外しがそれでも必要な理由は、 所有権システムです。derefメソッドが値への参照ではなく、値を直接返したら、値はselfから外にムーブされてしまいます。 今回の場合や、参照外し演算子を使用する多くの場合にはMyBoxの中の値の所有権を奪いたくはありません。 *演算子は、コードで*を打つたびに、ただ1回、derefメソッドの呼び出し、そして*演算子の呼び出しに置き換えられることに注意してください。 *演算子の置き換えは、無限に繰り返されないので、型i32に行き着き、リスト15-9でassert_eq!の5と合致します。","breadcrumbs":"スマートポインタ » Derefトレイトを実装して型を参照のように扱う","id":"273","title":"Derefトレイトを実装して型を参照のように扱う"},"274":{"body":"参照外し型強制 は、コンパイラが関数やメソッドの実引数に行う便利なものです。参照外し型強制は、 Derefを実装する型への参照をDerefが元の型を変換できる型への参照に変換します。参照外し型強制は、 特定の型の値への参照を関数やメソッド定義の引数型と一致しない引数として関数やメソッドに渡すときに自動的に発生します。 一連のderefメソッドの呼び出しが、提供した型を引数が必要とする型に変換します。 参照外し型強制は、関数やメソッド呼び出しを書くプログラマが&や*を多くの明示的な参照や参照外しとして追記する必要がないように、 Rustに追加されました。また、参照外し型強制のおかげで参照あるいはスマートポインタのどちらかで動くコードをもっと書くことができます。 参照外し型強制が実際に動いていることを確認するため、リスト15-8で定義したMyBoxと、 リスト15-10で追加したDerefの実装を使用しましょう。リスト15-11は、 文字列スライス引数のある関数の定義を示しています: ファイル名: src/main.rs fn hello(name: &str) { println!(\"Hello, {}!\", name);\n} リスト15-11: 型&strの引数nameのあるhello関数 hello関数は、文字列スライスを引数として呼び出すことができます。例えば、hello(\"Rust\")などです。 参照外し型強制により、helloを型MyBoxの値への参照とともに呼び出すことができます。リスト15-12のようにですね: ファイル名: src/main.rs # use std::ops::Deref;\n#\n# struct MyBox(T);\n#\n# impl MyBox {\n# fn new(x: T) -> MyBox {\n# MyBox(x)\n# }\n# }\n#\n# impl Deref for MyBox {\n# type Target = T;\n#\n# fn deref(&self) -> &T {\n# &self.0\n# }\n# }\n#\n# fn hello(name: &str) {\n# println!(\"Hello, {}!\", name);\n# }\n#\nfn main() { let m = MyBox::new(String::from(\"Rust\")); hello(&m);\n} リスト15-12: helloをMyBox値とともに呼び出し、参照外し型強制のおかげで動く ここで、hello関数を引数&mとともに呼び出しています。この引数は、MyBox値への参照です。 リスト15-10でMyBoxにDerefトレイトを実装したので、コンパイラはderefを呼び出すことで、 &MyBoxを&Stringに変換できるのです。標準ライブラリは、Stringに文字列スライスを返すDerefの実装を提供していて、 この実装は、DerefのAPIドキュメンテーションに載っています。コンパイラはさらにderefを呼び出して、 &Stringを&strに変換し、これはhello関数の定義と合致します。 Rustに参照外し型強制が実装されていなかったら、リスト15-12のコードの代わりにリスト15-13のコードを書き、 型&MyBoxの値でhelloを呼び出さなければならなかったでしょう。 ファイル名: src/main.rs # use std::ops::Deref;\n#\n# struct MyBox(T);\n#\n# impl MyBox {\n# fn new(x: T) -> MyBox {\n# MyBox(x)\n# }\n# }\n#\n# impl Deref for MyBox {\n# type Target = T;\n#\n# fn deref(&self) -> &T {\n# &self.0\n# }\n# }\n#\n# fn hello(name: &str) {\n# println!(\"Hello, {}!\", name);\n# }\n#\nfn main() { let m = MyBox::new(String::from(\"Rust\")); hello(&(*m)[..]);\n} リスト15-13: Rustに参照外し型強制がなかった場合に書かなければならないであろうコード (*m)がMyBoxをStringに参照外ししています。そして、&と[..]により、 文字列全体と等しいStringの文字列スライスを取り、helloのシグニチャと一致するわけです。 参照外し型強制のないコードは、これらの記号が関係するので、読むのも書くのも理解するのもより難しくなります。 参照外し型強制により、コンパイラはこれらの変換を自動的に扱えるのです。 Derefトレイトが関係する型に定義されていると、コンパイラは、型を分析し必要なだけDeref::derefを使用して、 参照を得、引数の型と一致させます。Deref::derefが挿入される必要のある回数は、コンパイル時に解決されるので、 参照外し型強制を活用するための実行時の代償は何もありません。","breadcrumbs":"スマートポインタ » 関数やメソッドで暗黙的な参照外し型強制","id":"274","title":"関数やメソッドで暗黙的な参照外し型強制"},"275":{"body":"Derefトレイトを使用して不変参照に対して*をオーバーライドするように、 DerefMutトレイトを使用して可変参照の*演算子をオーバーライドできます。 以下の3つの場合に型やトレイト実装を見つけた時にコンパイラは、参照外し型強制を行います: T: Derefの時、&Tから&U T: DerefMutの時、&mut Tから&mut U T: Derefの時、&mut Tから&U 前者2つは、可変性を除いて一緒です。最初のケースは、&Tがあり、Tが何らかの型UへのDerefを実装しているなら、 透過的に&Uを得られると述べています。2番目のケースは、同じ参照外し型強制が可変参照についても起こることを述べています。 3番目のケースはもっと巧妙です: Rustはさらに、可変参照を不変参照にも型強制するのです。ですが、逆はできま せん : 不変参照は、絶対に可変参照に型強制されないのです。借用規則により、可変参照があるなら、 その可変参照がそのデータへの唯一の参照に違いありません(でなければ、プログラムはコンパイルできません)。 1つの可変参照を1つの不変参照に変換することは、借用規則を絶対に破壊しません。 不変参照を可変参照にするには、そのデータへの不変参照がたった1つしかないことが必要ですが、 借用規則はそれを保証してくれません。故に、不変参照を可変参照に変換することが可能であるという前提を敷けません。","breadcrumbs":"スマートポインタ » 参照外し型強制が可変性と相互作用する方法","id":"275","title":"参照外し型強制が可変性と相互作用する方法"},"276":{"body":"スマートポインタパターンにとって重要な2番目のトレイトは、Dropであり、 これのおかげで値がスコープを抜けそうになった時に起こることをカスタマイズできます。 どんな型に対してもDropトレイトの実装を提供することができ、指定したコードは、 ファイルやネットワーク接続などのリソースを解放するのに活用できます。 Dropをスマートポインタの文脈で導入しています。Dropトレイトの機能は、ほぼ常にスマートポインタを実装する時に使われるからです。 例えば、BoxはDropをカスタマイズしてボックスが指しているヒープの領域を解放しています。 ある言語では、プログラマがスマートポインタのインスタンスを使い終わる度にメモリやリソースを解放するコードを呼ばなければなりません。 忘れてしまったら、システムは詰め込みすぎになりクラッシュする可能性があります。Rustでは、 値がスコープを抜ける度に特定のコードが走るよう指定でき、コンパイラはこのコードを自動的に挿入します。 結果として、特定の型のインスタンスを使い終わったプログラムの箇所全部にクリーンアップコードを配置するのに配慮する必要はありません。 それでもリソースをリークすることはありません。 Dropトレイトを実装することで値がスコープを抜けた時に走るコードを指定してください。 Dropトレイトは、selfへの可変参照を取るdropという1つのメソッドを実装する必要があります。 いつRustがdropを呼ぶのか確認するために、今はprintln!文のあるdropを実装しましょう。 リスト15-14は、唯一の独自の機能が、インスタンスがスコープを抜ける時にDropping CustomSmartPointer!と出力するだけの、 CustomSmartPointer構造体です。この例は、コンパイラがいつdrop関数を走らせるかをデモしています。 ファイル名: src/main.rs struct CustomSmartPointer { data: String,\n} impl Drop for CustomSmartPointer { fn drop(&mut self) { // CustomSmartPointerをデータ`{}`とともにドロップするよ println!(\"Dropping CustomSmartPointer with data `{}`!\", self.data); }\n} fn main() { let c = CustomSmartPointer { data: String::from(\"my stuff\") }; // 俺のもの let d = CustomSmartPointer { data: String::from(\"other stuff\") }; // 別のもの println!(\"CustomSmartPointers created.\"); // CustomSmartPointerが生成された\n} リスト15-14: クリーンアップコードを配置するDropトレイトを実装するCustomSmartPointer構造体 Dropトレイトは、初期化処理に含まれるので、インポートする必要はありません。 CustomSmartPointerにDropトレイトを実装し、println!を呼び出すdropメソッドの実装を提供しています。 drop関数の本体は、自分の型のインスタンスがスコープを抜ける時に走らせたいあらゆるロジックを配置する場所です。 ここで何らかのテキストを出力し、コンパイラがいつdropを呼ぶのかデモしています。 mainで、CustomSmartPointerのインスタンスを2つ作り、それからCustomSmartPointers created.と出力しています。 mainの最後で、CustomSmartPointerのインスタンスはスコープを抜け、コンパイラは最後のメッセージを出力しながら、 dropメソッドに置いたコードを呼び出します。dropメソッドを明示的に呼び出す必要はなかったことに注意してください。 このプログラムを実行すると、以下のような出力が出ます: CustomSmartPointers created.\nDropping CustomSmartPointer with data `other stuff`!\nDropping CustomSmartPointer with data `my stuff`! インスタンスがスコープを抜けた時に指定したコードを呼び出しながらコンパイラは、dropを自動的に呼び出してくれました。 変数は、生成されたのと逆の順序でドロップされるので、dはcより先にドロップされました。 この例は、dropメソッドの動き方を見た目で案内するだけですが、通常は、メッセージ出力ではなく、 自分の型が走らせる必要のあるクリーンアップコードを指定するでしょう。","breadcrumbs":"スマートポインタ » Dropトレイトで片付け時にコードを走らせる","id":"276","title":"Dropトレイトで片付け時にコードを走らせる"},"277":{"body":"残念ながら、自動的なdrop機能を無効化することは、単純ではありません。通常、dropを無効化する必要はありません; Dropトレイトの最重要な要点は、自動的に考慮されることです。ですが、時として、値を早期に片付けたくなる可能性があります。 一例は、ロックを管理するスマートポインタを使用する時です: 同じスコープの他のコードがロックを獲得できるように、 ロックを解放するdropメソッドを強制的に走らせたくなる可能性があります。Rustは、 Dropトレイトのdropメソッドを手動で呼ばせてくれません; スコープが終わる前に値を強制的にドロップさせたいなら、 代わりに標準ライブラリが提供するstd::mem::drop関数を呼ばなければなりません。 リスト15-14のmain関数を変更して手動でDropトレイトのdropメソッドを呼び出そうとしたら、 コンパイルエラーになるでしょう。リスト15-15のようにですね: ファイル名: src/main.rs fn main() { let c = CustomSmartPointer { data: String::from(\"some data\") }; println!(\"CustomSmartPointer created.\"); c.drop(); // mainの終端の前にCustomSmartPointerがドロップされた println!(\"CustomSmartPointer dropped before the end of main.\");\n} リスト15-15: Dropトレイトからdropメソッドを手動で呼び出し、早期に片付けようとする このコードをコンパイルしてみようとすると、こんなエラーが出ます: error[E0040]: explicit use of destructor method\n(エラー: デストラクタメソッドを明示的に使用しています) --> src/main.rs:14:7 |\n14 | c.drop(); | ^^^^ explicit destructor calls not allowed 明示的にdropを呼び出すことは許されていないことをこのエラーメッセージは述べています。 エラーメッセージは デストラクタ という専門用語を使っていて、これは、 インスタンスを片付ける関数の一般的なプログラミング専門用語です。 デストラクタ は、 コンストラクタ に類似していて、これはインスタンスを生成します。Rustのdrop関数は、 1種の特定のデストラクタです。 コンパイラはそれでも、mainの終端で値に対して自動的にdropを呼び出すので、dropを明示的に呼ばせてくれません。 コンパイラが2回同じ値を片付けようとするので、これは 二重解放 エラーになるでしょう。 値がスコープを抜けるときにdropが自動的に挿入されるのを無効化できず、dropメソッドを明示的に呼ぶこともできません。 よって、値を早期に片付けさせる必要があるなら、std::mem::drop関数を使用できます。 std::mem::drop関数は、Dropトレイトのdropメソッドとは異なります。 早期に強制的にドロップさせたい値を引数で渡すことで呼びます。この関数は初期化処理に含まれているので、 リスト15-15のmainを変更してdrop関数を呼び出せます。リスト15-16のようにですね: ファイル名: src/main.rs # struct CustomSmartPointer {\n# data: String,\n# }\n#\n# impl Drop for CustomSmartPointer {\n# fn drop(&mut self) {\n# println!(\"Dropping CustomSmartPointer!\");\n# }\n# }\n#\nfn main() { let c = CustomSmartPointer { data: String::from(\"some data\") }; println!(\"CustomSmartPointer created.\"); drop(c); // CustomSmartPointerはmainが終わる前にドロップされた println!(\"CustomSmartPointer dropped before the end of main.\");\n} リスト15-16: 値がスコープを抜ける前に明示的にドロップするためにstd::mem::dropを呼び出す このコードを実行すると、以下のように出力されます: CustomSmartPointer created.\nDropping CustomSmartPointer with data `some data`!\nCustomSmartPointer dropped before the end of main. Dropping CustomSmartPointer with data `some data`!というテキストが、 CustomSmartPointer created.とCustomSmartPointer dropped before the end of main.テキストの間に出力されるので、 dropメソッドのコードがその時点で呼び出されてcをドロップしたことを示しています。 Dropトレイト実装で指定されたコードをいろんな方法で使用し、片付けを便利で安全にすることができます: 例を挙げれば、これを使用して独自のメモリアロケータを作ることもできるでしょう!DropトレイトとRustの所有権システムがあれば、 コンパイラが自動的に行うので、片付けを覚えておく必要はなくなります。 まだ使用中の値を間違って片付けてしまうことに起因する問題を心配する必要もなくて済みます: 参照が常に有効であると確認してくれる所有権システムが、値が最早使用されなくなった時にdropが1回だけ呼ばれることを保証してくれるのです。 これでBoxとスマートポインタの特徴の一部を調査したので、標準ライブラリに定義されている他のスマートポインタをいくつか見ましょう。","breadcrumbs":"スマートポインタ » std::mem::dropで早期に値をドロップする","id":"277","title":"std::mem::dropで早期に値をドロップする"},"278":{"body":"大多数の場合、所有権は明らかです: 一体どの変数が与えられた値を所有しているかわかるのです。 ところが、単独の値が複数の所有者を持つ可能性のある場合もあります。例えば、グラフデータ構造では、 複数の辺が同じノードを指す可能性があり、概念的にそのノードはそれを指す全ての辺に所有されるわけです。 指す辺がなくならない限り、ノードは片付けられるべきではありません。 複数の所有権を可能にするため、RustにはRcという型があり、これは、 reference counting (参照カウント)の省略形です。 Rc型は、値がまだ使用中かどうか決定する値への参照の数を追跡します。値への参照が0なら、どの参照も無効にすることなく、 値は片付けられます。 Rcを家族部屋のテレビと想像してください。1人がテレビを見に部屋に入ったら、テレビをつけます。 他の人も部屋に入ってテレビを観ることができます。最後の人が部屋を離れる時、 もう使用されていないので、テレビを消します。他の人がまだ観ているのに誰かがテレビを消したら、 残りのテレビ視聴者が騒ぐでしょう! ヒープにプログラムの複数箇所で読む何らかのデータを確保したいけれど、 コンパイル時にはどの部分が最後にデータを使用し終わるか決定できない時にRc型を使用します。 どの部分が最後に終わるかわかっているなら、 単にその部分をデータの所有者にして、コンパイル時に強制される普通の所有権ルールが効果を発揮するでしょう。 Rcは、シングルスレッドの筋書きで使用するためだけのものであることに注意してください。 第16章で並行性について議論する時に、マルチスレッドプログラムで参照カウントをする方法を講義します。","breadcrumbs":"スマートポインタ » Rcは、参照カウント方式のスマートポインタ","id":"278","title":"Rcは、参照カウント方式のスマートポインタ"},"279":{"body":"リスト15-5のコンスリストの例に回帰しましょう。Boxを使って定義したことを思い出してください。 今回は、両方とも3番目のリストの所有権を共有する2つのリストを作成します。 これは概念的には図15-3のような見た目になります: 図15-3: 3番目のリスト、aの所有権を共有する2つのリスト、bとc 5と10を含むリストaを作ります。さらにもう2つリストを作ります: 3で始まるbと4で始まるcです。 bとcのどちらもそれから5と10を含む最初のaリストに続きます。換言すれば、 どちらのリストも5と10を含む最初のリストを共有しています。 Listの定義を使用してBoxとともにこの筋書きを実装しようとしても、うまくいきません。 リスト15-17のようにですね: ファイル名: src/main.rs enum List { Cons(i32, Box), Nil,\n} use List::{Cons, Nil}; fn main() { let a = Cons(5, Box::new(Cons(10, Box::new(Nil)))); let b = Cons(3, Box::new(a)); let c = Cons(4, Box::new(a));\n} リスト15-17: 3番目のリストの所有権を共有しようとするBoxを使った2つのリストを存在させることはできないとデモする このコードをコンパイルすると、こんなエラーが出ます: error[E0382]: use of moved value: `a` --> src/main.rs:13:30 |\n12 | let b = Cons(3, Box::new(a)); | - value moved here\n13 | let c = Cons(4, Box::new(a)); | ^ value used here after move | = note: move occurs because `a` has type `List`, which does not implement the `Copy` trait Cons列挙子は、保持しているデータを所有するので、bリストを作成する時に、 aがbにムーブされ、bがaを所有します。それからcを作る際に再度aを使用しようとすると、 aはムーブ済みなので、できないわけです。 Consの定義を代わりに参照を保持するように変更することもできますが、そうしたら、 ライフタイム引数を指定しなければなりません。ライフタイム引数を指定することで、 リストの各要素が最低でもリスト全体と同じ期間だけ生きることを指定することになります。 例えば、借用チェッカーはlet a = Cons(10, &Nil);をコンパイルさせてくれません。 一時的なNil値が、aが参照を得られるより前にドロップされてしまうからです。 代わりに、Listの定義をリスト15-18のように、Boxの箇所にRcを使うように変更します。 これで各Cons列挙子は、値とListを指すRcを保持するようになりました。bを作る際、 aの所有権を奪うのではなく、aが保持しているRcをクローンします。それによって、 参照の数が1から2に増え、aとbにそのRcにあるデータの所有権を共有させます。 また、cを生成する際にもaをクローンするので、参照の数は2から3になります。Rc::cloneを呼ぶ度に、 Rc内のデータの参照カウントが増え、参照が0にならない限りデータは片付けられません。 ファイル名: src/main.rs enum List { Cons(i32, Rc), Nil,\n} use List::{Cons, Nil};\nuse std::rc::Rc; fn main() { let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); let b = Cons(3, Rc::clone(&a)); let c = Cons(4, Rc::clone(&a));\n} リスト15-18: Rcを使用するListの定義 初期化処理に含まれていないので、use文を追加してRcをスコープに導入する必要があります。 mainで5と10を保持するリストを作成し、aの新しいRcに格納しています。それから、 bとcを作成する際に、Rc::clone関数を呼び出し、引数としてaのRcへの参照を渡しています。 Rc::clone(&a)ではなく、a.clone()を呼ぶこともできますが、Rustのしきたりは、この場合Rc::cloneを使うことです。 Rc::cloneの実装は、多くの型のclone実装のように、全てのデータのディープコピーをすることではありません。 Rc::cloneの呼び出しは、参照カウントをインクリメントするだけであり、時間はかかりません。 データのディープコピーは時間がかかることもあります。参照カウントにRc::cloneを使うことで、 視覚的にディープコピーをする類のクローンと参照カウントを増やす種類のクローンを区別することができます。 コード内でパフォーマンスの問題を探す際、ディープコピーのクローンだけを考慮し、Rc::cloneの呼び出しを無視できるのです。","breadcrumbs":"スマートポインタ » Rcでデータを共有する","id":"279","title":"Rcでデータを共有する"},"28":{"body":"プロジェクトを最終的にリリースする準備ができたら、cargo build --releaseを使用して、 最適化を行なってコンパイルすることができます。このコマンドは、 target/debug ではなく、 target/release に実行可能ファイルを作成します。最適化は、Rustコードの実行を速くしてくれますが、 オンにするとプログラムをコンパイルする時間が延びます。このため、2つの異なるプロファイルがあるのです: 頻繁に再ビルドをかけたい開発用と、繰り返し再ビルドすることはなく、できるだけ高速に動いてユーザにあげる最終的なプログラムをビルドする用です。 コードの実行時間をベンチマークするなら、cargo build --releaseを確実に実行し、 target/release の実行可能ファイルでベンチマークしてください。","breadcrumbs":"事始め » リリースビルドを行う","id":"28","title":"リリースビルドを行う"},"280":{"body":"aのRcへの参照を作ったりドロップする毎に参照カウントが変化するのが確かめられるように、 リスト15-18の動く例を変更しましょう。 リスト15-19で、リストcを囲む内側のスコープができるようmainを変更します; そうすれば、cがスコープを抜けるときに参照カウントがどう変化するか確認できます。 ファイル名: src/main.rs # enum List {\n# Cons(i32, Rc),\n# Nil,\n# }\n#\n# use List::{Cons, Nil};\n# use std::rc::Rc;\n#\nfn main() { let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); // a生成後のカウント = {} println!(\"count after creating a = {}\", Rc::strong_count(&a)); let b = Cons(3, Rc::clone(&a)); // b生成後のカウント = {} println!(\"count after creating b = {}\", Rc::strong_count(&a)); { let c = Cons(4, Rc::clone(&a)); // c生成後のカウント = {} println!(\"count after creating c = {}\", Rc::strong_count(&a)); } // cがスコープを抜けた後のカウント = {} println!(\"count after c goes out of scope = {}\", Rc::strong_count(&a));\n} リスト15-19: 参照カウントを出力する プログラム内で参照カウントが変更される度に、参照カウントを出力します。参照カウントは、 Rc::strong_count関数を呼び出すことで得られます。Rc型にはweak_countもあるので、 この関数はcountではなくstrong_countと命名されています; weak_countの使用目的は、 「循環参照を回避する」節で確かめます。 このコードは、以下の出力をします: count after creating a = 1\ncount after creating b = 2\ncount after creating c = 3\ncount after c goes out of scope = 2 aのRcは最初1という参照カウントであることがわかります; そして、cloneを呼び出す度に、 カウントは1ずつ上がります。cがスコープを抜けると、カウントは1下がります。参照カウントを増やすのに、 Rc::cloneを呼ばなければいけなかったみたいに参照カウントを減らすのに関数を呼び出す必要はありません: Rc値がスコープを抜けるときにDropトレイトの実装が自動的に参照カウントを減らします。 この例でわからないことは、bそしてaが、mainの終端でスコープを抜ける時に、カウントが0になり、 その時点でRcが完全に片付けられることです。Rcを使用すると、単独の値に複数の所有者を持たせることができ、 所有者のいずれかが存在している限り、値が有効であり続けることをカウントは保証します。 不変参照経由で、Rcは読み取り専用にプログラムの複数箇所間でデータを共有させてくれます。 Rcが複数の可変参照を存在させることも許可してくれたら、第4章で議論した借用ルールの1つを侵害する虞(おそれ)があります: 同じ場所への複数の可変借用は、データ競合や矛盾を引き起こすことがあるのです。しかし、 データを可変化する能力はとても有用です!次の節では、内部可変性パターンと、 Rcと絡めて使用してこの不変性制限を手がけられるRefCell型について議論します。","breadcrumbs":"スマートポインタ » Rcをクローンすると、参照カウントが増える","id":"280","title":"Rcをクローンすると、参照カウントが増える"},"281":{"body":"内部可変性は、そのデータへの不変参照がある時でさえもデータを可変化できるRustでのデザインパターンです: 普通、この行動は借用規則により許可されません。データを可変化するために、このパターンは、データ構造内でunsafeコードを使用して、 可変性と借用を支配するRustの通常の規則を捻じ曲げています。まだ、unsafeコードについては講義していません; 第19章で行います。たとえ、コンパイラが保証できなくても、借用規則に実行時に従うことが保証できる時、 内部可変性パターンを使用した型を使用できます。関係するunsafeコードはそうしたら、安全なAPIにラップされ、 外側の型は、それでも不変です。 内部可変性パターンに従うRefCell型を眺めてこの概念を探究しましょう。","breadcrumbs":"スマートポインタ » RefCellと内部可変性パターン","id":"281","title":"RefCellと内部可変性パターン"},"282":{"body":"Rcと異なり、RefCell型は、保持するデータに対して単独の所有権を表します。では、 どうしてRefCellがBoxのような型と異なるのでしょうか?第4章で学んだ借用規則を思い出してください: いかなる時も(以下の両方ではなく、)1つの可変参照かいくつもの不変参照の どちらか が可能になる 参照は常に有効でなければならない。 参照とBoxでは、借用規則の不変条件は、コンパイル時に強制されています。RefCellでは、 これらの不変条件は、 実行時に 強制されます。参照でこれらの規則を破ったら、コンパイルエラーになりました。 RefCellでこれらの規則を破ったら、プログラムはパニックし、終了します。 コンパイル時に借用規則を精査することの利点は、エラーが開発過程の早い段階で捕捉されることと、 あらかじめ全ての分析が終わるので、実行パフォーマンスへの影響がないことです。それらの理由により、 多くの場合でコンパイル時に借用規則を精査することが最善の選択肢であり、これがRustの既定になっているのです。 借用規則を実行時に代わりに精査する利点は、コンパイル時の精査では許容されない特定のメモリ安全な筋書きが許容されることです。 Rustコンパイラのような静的解析は、本質的に保守的です。コードの特性には、コードを解析するだけでは検知できないものもあります: 最も有名な例は停止性問題であり、この本の範疇を超えていますが、調べると面白い話題です。 不可能な分析もあるので、Rustのコンパイラが、コードが所有権規則に応じていると確証を得られない場合、 正しいプログラムを拒否する可能性があります; このように、保守的なのです。コンパイラが不正なプログラムを受け入れたら、 ユーザは、コンパイラが行う保証を信じることはできなくなるでしょう。しかしながら、 コンパイラが正当なプログラムを拒否するのなら、プログラマは不便に思うでしょうが、悲劇的なことは何も起こり得ません。 コードが借用規則に従っているとプログラマは確証を得ているが、コンパイラがそれを理解し保証することができない時に RefCell型は有用です。 Rcと類似して、RefCellもシングルスレッドの筋書きで使用するためのものであり、 試しにマルチスレッドの文脈で使ってみようとすると、コンパイルエラーを出します。 RefCellの機能をマルチスレッドのプログラムで得る方法については、第16章で語ります。 こちらにBox, Rc, RefCellを選択する理由を要約しておきます: Rcは、同じデータに複数の所有者を持たせてくれる; BoxとRefCellは単独の所有者。 Boxでは、不変借用も可変借用もコンパイル時に精査できる; Rcでは不変借用のみがコンパイル時に精査できる; RefCellでは、不変借用も可変借用も実行時に精査される。 RefCellは実行時に精査される可変借用を許可するので、RefCellが不変でも、 RefCell内の値を可変化できる。 不変な値の中の値を可変化することは、 内部可変性 パターンです。内部可変性が有用になる場面を見て、 それが可能になる方法を調査しましょう。","breadcrumbs":"スマートポインタ » RefCellで実行時に借用規則を強制する","id":"282","title":"RefCellで実行時に借用規則を強制する"},"283":{"body":"借用規則の結果は、不変値がある時、可変で借用することはできないということです。 例えば、このコードはコンパイルできません: fn main() { let x = 5; let y = &mut x;\n} このコードをコンパイルしようとしたら、以下のようなエラーが出るでしょう: error[E0596]: cannot borrow immutable local variable `x` as mutable\n(エラー: 不変なローカル変数`x`を可変で借用することはできません) --> src/main.rs:3:18 |\n2 | let x = 5; | - consider changing this to `mut x`\n3 | let y = &mut x; | ^ cannot borrow mutably ですが、メソッド内で値が自身を可変化するけれども、他のコードにとっては、 不変に見えることが有用な場面もあります。その値のメソッドの外のコードは、その値を可変化することはできないでしょう。 RefCellを使うことは、内部可変性を取得する能力を得る1つの方法です。しかし、 RefCellは借用規則を完全に回避するものではありません: コンパイラの借用チェッカーは、内部可変性を許可し、 借用規則は代わりに実行時に精査されます。この規則を侵害したら、コンパイルエラーではなくpanic!になるでしょう。 RefCellを使用して不変値を可変化する実践的な例に取り組み、それが役に立つ理由を確認しましょう。 内部可変性のユースケース: モックオブジェクト テストダブル は、テスト中に別の型の代わりに使用される型の一般的なプログラミングの概念です。 モックオブジェクト は、テスト中に起きることを記録するテストダブルの特定の型なので、 正しい動作が起きたことをアサートできます。 編注: テストダブルとは、ソフトウェアテストにおいて、テスト対象が依存しているコンポーネントを置き換える代用品のこと。 Rustには、他の言語でいうオブジェクトは存在せず、また、他の言語のように標準ライブラリにモックオブジェクトの機能が組み込まれてもいません。 ですが、同じ目的をモックオブジェクトとして提供する構造体を作成することは確実にできます。 以下が、テストを行う筋書きです: 値を最大値に対して追跡し、現在値がどれくらい最大値に近いかに基づいてメッセージを送信するライブラリを作成します。 このライブラリは、ユーザが行うことのできるAPIコールの数の割り当てを追跡するのに使用することができるでしょう。 作成するライブラリは、値がどれくらい最大に近いかと、いつどんなメッセージになるべきかを追いかける機能を提供するだけです。 このライブラリを使用するアプリケーションは、メッセージを送信する機構を提供すると期待されるでしょう: アプリケーションは、アプリケーションにメッセージを置いたり、メールを送ったり、テキストメッセージを送るなどできるでしょう。 ライブラリはその詳細を知る必要はありません。必要なのは、提供するMessengerと呼ばれるトレイトを実装している何かなのです。 リスト15-20は、ライブラリのコードを示しています: ファイル名: src/lib.rs pub trait Messenger { fn send(&self, msg: &str);\n} pub struct LimitTracker<'a, T: 'a + Messenger> { messenger: &'a T, value: usize, max: usize,\n} impl<'a, T> LimitTracker<'a, T> where T: Messenger { pub fn new(messenger: &T, max: usize) -> LimitTracker { LimitTracker { messenger, value: 0, max, } } pub fn set_value(&mut self, value: usize) { self.value = value; let percentage_of_max = self.value as f64 / self.max as f64; if percentage_of_max >= 0.75 && percentage_of_max < 0.9 { // 警告: 割り当ての75%以上を使用してしまいました self.messenger.send(\"Warning: You've used up over 75% of your quota!\"); } else if percentage_of_max >= 0.9 && percentage_of_max < 1.0 { // 切迫した警告: 割り当ての90%以上を使用してしまいました self.messenger.send(\"Urgent warning: You've used up over 90% of your quota!\"); } else if percentage_of_max >= 1.0 { // エラー: 割り当てを超えています self.messenger.send(\"Error: You are over your quota!\"); } }\n} リスト15-20: 値が最大値にどれくらい近いかを追跡し、特定のレベルの時に警告するライブラリ このコードの重要な部分の1つは、Messengerトレイトには、selfへの不変参照とメッセージのテキストを取るsendというメソッドが1つあることです。 これが、モックオブジェクトが持つ必要のあるインターフェイスなのです。もう1つの重要な部分は、 LimitTrackerのset_valueメソッドの振る舞いをテストしたいということです。value引数に渡すものを変えることができますが、 set_valueはアサートを行えるものは何も返してくれません。LimitTrackerをMessengerトレイトを実装する何かと、 maxの特定の値で生成したら、valueに異なる数値を渡した時にメッセンジャーは適切なメッセージを送ると指示されると言えるようになりたいです。 sendを呼び出す時にメールやテキストメッセージを送る代わりに送ると指示されたメッセージを追跡するだけのモックオブジェクトが必要です。 モックオブジェクトの新規インスタンスを生成し、モックオブジェクトを使用するLimitTrackerを生成し、 LimitTrackerのset_valueを呼び出し、それからモックオブジェクトに期待しているメッセージがあることを確認できます。 リスト15-21は、それだけをするモックオブジェクトを実装しようとするところを示しますが、借用チェッカーが許可してくれません: ファイル名: src/lib.rs #[cfg(test)]\nmod tests { use super::*; struct MockMessenger { sent_messages: Vec, } impl MockMessenger { fn new() -> MockMessenger { MockMessenger { sent_messages: vec![] } } } impl Messenger for MockMessenger { fn send(&self, message: &str) { self.sent_messages.push(String::from(message)); } } #[test] fn it_sends_an_over_75_percent_warning_message() { let mock_messenger = MockMessenger::new(); let mut limit_tracker = LimitTracker::new(&mock_messenger, 100); limit_tracker.set_value(80); assert_eq!(mock_messenger.sent_messages.len(), 1); }\n} リスト15-21: 借用チェッカーが許可してくれないMockMessengerを実装しようとする このテストコードはStringのVecで送信すると指示されたメッセージを追跡するsent_messagesフィールドのあるMockMessenger構造体を定義しています。 また、空のメッセージリストから始まる新しいMockMessenger値を作るのを便利にしてくれる関連関数のnewも定義しています。 それからMockMessengerにMessengerトレイトを実装しているので、LimitTrackerにMockMessengerを与えられます。 sendメソッドの定義で引数として渡されたメッセージを取り、sent_messagesのMockMessengerリストに格納しています。 テストでは、max値の75%以上になる何かにvalueをセットしろとLimitTrackerが指示される時に起きることをテストしています。 まず、新しいMockMessengerを生成し、空のメッセージリストから始まります。そして、 新しいLimitTrackerを生成し、新しいMockMessengerの参照と100というmax値を与えます。 LimitTrackerのset_valueメソッドは80という値で呼び出し、これは100の75%を上回っています。 そして、MockMessengerが追いかけているメッセージのリストが、今は1つのメッセージを含んでいるはずとアサートします。 ところが、以下のようにこのテストには1つ問題があります: error[E0596]: cannot borrow immutable field `self.sent_messages` as mutable\n(エラー: 不変なフィールド`self.sent_messages`を可変で借用できません) --> src/lib.rs:52:13 |\n51 | fn send(&self, message: &str) { | ----- use `&mut self` here to make mutable\n52 | self.sent_messages.push(String::from(message)); | ^^^^^^^^^^^^^^^^^^ cannot mutably borrow immutable field sendメソッドはselfへの不変参照を取るので、MockMessengerを変更してメッセージを追跡できないのです。 代わりに&mut selfを使用するというエラーテキストからの提言を選ぶこともできないのです。 そうしたら、sendのシグニチャが、Messengerトレイト定義のシグニチャと一致しなくなるからです(気軽に試してエラーメッセージを確認してください)。 これは、内部可変性が役に立つ場面なのです!sent_messagesをRefCell内部に格納し、 そうしたらsendメッセージは、sent_messagesを変更して見かけたメッセージを格納できるようになるでしょう。 リスト15-22は、それがどんな感じかを示しています: ファイル名: src/lib.rs #[cfg(test)]\nmod tests { use super::*; use std::cell::RefCell; struct MockMessenger { sent_messages: RefCell>, } impl MockMessenger { fn new() -> MockMessenger { MockMessenger { sent_messages: RefCell::new(vec![]) } } } impl Messenger for MockMessenger { fn send(&self, message: &str) { self.sent_messages.borrow_mut().push(String::from(message)); } } #[test] fn it_sends_an_over_75_percent_warning_message() { // --snip--\n# let mock_messenger = MockMessenger::new();\n# let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);\n# limit_tracker.set_value(75); assert_eq!(mock_messenger.sent_messages.borrow().len(), 1); }\n} リスト15-22: 外側の値は不変と考えられる一方でRefCellで内部の値を可変化する さて、sent_messagesフィールドは、Vecではなく、型RefCell>になりました。 new関数で、空のベクタの周りにRefCell>を新しく作成しています。 sendメソッドの実装については、最初の引数はそれでもselfへの不変借用で、トレイト定義と合致しています。 RefCell>のborrow_mutをself.sent_messagesに呼び出し、 RefCell>の中の値への可変参照を得て、これはベクタになります。 それからベクタへの可変参照にpushを呼び出して、テスト中に送られるメッセージを追跡しています。 行わなければならない最後の変更は、アサート内部にあります: 内部のベクタにある要素の数を確認するため、 RefCell>にborrowを呼び出し、ベクタへの不変参照を得ています。 RefCellの使用法を見かけたので、動作の仕方を深掘りしましょう! RefCellで実行時に借用を追いかける 不変および可変参照を作成する時、それぞれ&と&mut記法を使用します。RefCellでは、 borrowとborrow_mutメソッドを使用し、これらはRefCellに所属する安全なAPIの一部です。 borrowメソッドは、スマートポインタ型のRefを返し、borrow_mutはスマートポインタ型のRefMutを返します。 どちらの型もDerefを実装しているので、普通の参照のように扱うことができます。 RefCellは、現在活動中のRefとRefMutスマートポインタの数を追いかけます。 borrowを呼び出す度に、RefCellは活動中の不変参照の数を増やします。Refの値がスコープを抜けたら、 不変参照の数は1下がります。コンパイル時の借用規則と全く同じように、RefCellはいかなる時も、 複数の不変借用または1つの可変借用を持たせてくれるのです。 これらの規則を侵害しようとすれば、参照のようにコンパイルエラーになるのではなく、 RefCellの実装は実行時にパニックするでしょう。リスト15-23は、リスト15-22のsend実装に対する変更を示しています。 同じスコープで2つの可変借用が活動するようわざと生成し、RefCellが実行時にこれをすることを阻止してくれるところを説明しています。 ファイル名: src/lib.rs impl Messenger for MockMessenger { fn send(&self, message: &str) { let mut one_borrow = self.sent_messages.borrow_mut(); let mut two_borrow = self.sent_messages.borrow_mut(); one_borrow.push(String::from(message)); two_borrow.push(String::from(message)); }\n} リスト15-23: 同じスコープで2つの可変参照を生成してRefCellがパニックすることを確かめる borrow_mutから返ってきたRefMutスマートポインタに対して変数one_borrowを生成しています。 そして、同様にして変数two_borrowにも別の可変借用を生成しています。これにより同じスコープで2つの可変参照ができ、 これは許可されないことです。このテストを自分のライブラリ用に走らせると、リスト15-23のコードはエラーなくコンパイルできますが、 テストは失敗するでしょう: ---- tests::it_sends_an_over_75_percent_warning_message stdout ---- thread 'tests::it_sends_an_over_75_percent_warning_message' panicked at\n'already borrowed: BorrowMutError', src/libcore/result.rs:906:4 (スレッド'tests::it_sends_an_over_75_percent_warning_message'は、\n'すでに借用されています: BorrowMutError', src/libcore/result.rs:906:4でパニックしました)\nnote: Run with `RUST_BACKTRACE=1` for a backtrace. コードは、already borrowed: BorrowMutErrorというメッセージとともにパニックしたことに注目してください。 このようにしてRefCellは実行時に借用規則の侵害を扱うのです。 コンパイル時ではなく実行時に借用エラーをキャッチするということは、開発過程の遅い段階でコードのミスを発見し、 コードをプロダクションにデプロイする時まで発見しない可能性もあることを意味します。また、 コンパイル時ではなく、実行時に借用を追いかける結果として、少し実行時にパフォーマンスを犠牲にするでしょう。 しかしながら、RefCellを使うことで、不変値のみが許可される文脈で使用しつつ、 自身を変更して見かけたメッセージを追跡するモックオブジェクトを書くことが可能になります。 代償はありますが、RefCellを使用すれば、普通の参照よりも多くの機能を得ることができるわけです。","breadcrumbs":"スマートポインタ » 内部可変性: 不変値への可変借用","id":"283","title":"内部可変性: 不変値への可変借用"},"284":{"body":"RefCellの一般的な使用法は、Rcと組み合わせることにあります。Rcは何らかのデータに複数の所有者を持たせてくれるけれども、 そのデータに不変のアクセスしかさせてくれないことを思い出してください。RefCellを抱えるRcがあれば、 複数の所有者を持ち そして 、可変化できる値を得ることができるのです。 例を挙げれば、Rcを使用して複数のリストに別のリストの所有権を共有させたリスト15-18のコンスリストの例を思い出してください。 Rcは不変値だけを抱えるので、一旦生成したら、リストの値はどれも変更できません。RefCellを含めて、 リストの値を変更する能力を得ましょう。RefCellをCons定義で使用することで、 リスト全てに格納されている値を変更できることをリスト15-24は示しています: ファイル名: src/main.rs #[derive(Debug)]\nenum List { Cons(Rc>, Rc), Nil,\n} use List::{Cons, Nil};\nuse std::rc::Rc;\nuse std::cell::RefCell; fn main() { let value = Rc::new(RefCell::new(5)); let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil))); let b = Cons(Rc::new(RefCell::new(6)), Rc::clone(&a)); let c = Cons(Rc::new(RefCell::new(10)), Rc::clone(&a)); *value.borrow_mut() += 10; println!(\"a after = {:?}\", a); println!(\"b after = {:?}\", b); println!(\"c after = {:?}\", c);\n} リスト15-24: Rc>で可変化できるListを生成する Rc>のインスタンスの値を生成し、valueという名前の変数に格納しているので、 直接後ほどアクセスすることができます。そして、aにvalueを持つCons列挙子でListを生成しています。 valueからaに所有権を移したり、aがvalueから借用するのではなく、aとvalueどちらにも中の5の値の所有権を持たせるよう、 valueをクローンする必要があります。 リストaをRcに包んでいるので、リストbとcを生成する時に、どちらもaを参照できます。 リスト15-18ではそうしていました。 a、b、cのリストを作成した後、valueの値に10を足しています。これをvalueのborrow_mutを呼び出すことで行い、 これは、第5章で議論した自動参照外し機能(「->演算子はどこに行ったの?」節をご覧ください)を使用して、 Rcを内部のRefCell値に参照外ししています。borrow_mutメソッドは、 RefMutスマートポインタを返し、それに対して参照外し演算子を使用し、中の値を変更します。 a、b、cを出力すると、全て5ではなく、変更された15という値になっていることがわかります。 a after = Cons(RefCell { value: 15 }, Nil)\nb after = Cons(RefCell { value: 6 }, Cons(RefCell { value: 15 }, Nil))\nc after = Cons(RefCell { value: 10 }, Cons(RefCell { value: 15 }, Nil)) このテクニックは非常に綺麗です!RefCellを使用することで表面上は不変なList値を持てます。 しかし、内部可変性へのアクセスを提供するRefCellのメソッドを使用できるので、必要な時にはデータを変更できます。 借用規則を実行時に精査することでデータ競合を防ぎ、時としてデータ構造でちょっとのスピードを犠牲にこの柔軟性を得るのは価値があります。 標準ライブラリには、Cellなどの内部可変性を提供する他の型もあり、この型は、内部値への参照を与える代わりに、 値はCellの内部や外部へコピーされる点を除き似ています。またMutexもあり、 これはスレッド間で使用するのが安全な内部可変性を提供します; 第16章でその使いみちについて議論しましょう。 これらの型の違いをより詳しく知るには、標準ライブラリのドキュメンテーションをチェックしてください。","breadcrumbs":"スマートポインタ » RcとRefCellを組み合わせることで可変なデータに複数の所有者を持たせる","id":"284","title":"RcとRefCellを組み合わせることで可変なデータに複数の所有者を持たせる"},"285":{"body":"Rustのメモリ安全保証により誤って絶対に片付けられることのないメモリ( メモリリーク として知られています)を生成してしまいにくくなりますが、 不可能にはなりません。コンパイル時にデータ競合を防ぐのと同じようにメモリリークを完全に回避することは、 Rustの保証の一つではなく、メモリリークはRustにおいてはメモリ安全であることを意味します。 Rustでは、RcとRefCellを使用してメモリリークを許可するとわかります: 要素がお互いに循環して参照する参照を生成することも可能ということです。循環の各要素の参照カウントが絶対に0にならないので、 これはメモリリークを起こし、値は絶対にドロップされません。","breadcrumbs":"スマートポインタ » 循環参照は、メモリをリークすることもある","id":"285","title":"循環参照は、メモリをリークすることもある"},"286":{"body":"リスト15-25のList enumの定義とtailメソッドから始めて、どう循環参照が起こる可能性があるのかとその回避策を見ましょう: ファイル名: src/main.rs # fn main() {}\nuse std::rc::Rc;\nuse std::cell::RefCell;\nuse List::{Cons, Nil}; #[derive(Debug)]\nenum List { Cons(i32, RefCell>), Nil,\n} impl List { fn tail(&self) -> Option<&RefCell>> { match *self { Cons(_, ref item) => Some(item), Nil => None, } }\n} リスト15-25: Cons列挙子が参照しているものを変更できるようにRefCellを抱えているコンスリストの定義 リスト15-5のList定義の別バリエーションを使用しています。Cons列挙子の2番目の要素はこれでRefCell>になり、 リスト15-24のようにi32値を変更する能力があるのではなく、Cons列挙子が指しているList値の先を変えたいということです。 また、tailメソッドを追加してCons列挙子があるときに2番目の要素にアクセスするのが便利になるようにしています。 リスト15-26でリスト15-25の定義を使用するmain関数を追加しています。このコードは、aにリストを、 bにaのリストを指すリストを作成します。それからaのリストを変更してbを指し、循環参照させます。 その流れの中に過程のいろんな場所での参照カウントを示すprintln!文が存在しています。 ファイル名: src/main.rs # use List::{Cons, Nil};\n# use std::rc::Rc;\n# use std::cell::RefCell;\n# #[derive(Debug)]\n# enum List {\n# Cons(i32, RefCell>),\n# Nil,\n# }\n#\n# impl List {\n# fn tail(&self) -> Option<&RefCell>> {\n# match *self {\n# Cons(_, ref item) => Some(item),\n# Nil => None,\n# }\n# }\n# }\n#\nfn main() { let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil)))); // aの最初の参照カウント = {} println!(\"a initial rc count = {}\", Rc::strong_count(&a)); // aの次の要素は = {:?} println!(\"a next item = {:?}\", a.tail()); let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a)))); // b作成後のaの参照カウント = {} println!(\"a rc count after b creation = {}\", Rc::strong_count(&a)); // bの最初の参照カウント = {} println!(\"b initial rc count = {}\", Rc::strong_count(&b)); // bの次の要素 = {:?} println!(\"b next item = {:?}\", b.tail()); if let Some(link) = a.tail() { *link.borrow_mut() = Rc::clone(&b); } // aを変更後のbの参照カウント = {} println!(\"b rc count after changing a = {}\", Rc::strong_count(&b)); // aを変更後のaの参照カウント = {} println!(\"a rc count after changing a = {}\", Rc::strong_count(&a)); // Uncomment the next line to see that we have a cycle; // it will overflow the stack // 次の行のコメントを外して循環していると確認してください; スタックオーバーフローします // println!(\"a next item = {:?}\", a.tail()); // aの次の要素 = {:?}\n} リスト15-26: 2つのList値がお互いを指して循環参照する 最初のリストが5, NilのList値を保持するRcインスタンスを変数aに生成します。 そして、値10とaのリストを指す別のList値を保持するRcインスタンスを変数bに生成します。 aがNilではなくbを指すように変更して、循環させます。tailメソッドを使用して、 aのRefCell>への参照を得ることで循環させて、この参照は変数linkに配置します。 それからRefCell>のborrow_mutメソッドを使用して中の値をNil値を持つRcから、 bのRcに変更します。 最後のprintln!を今だけコメントアウトしたまま、このコードを実行すると、こんな出力が得られます: a initial rc count = 1\na next item = Some(RefCell { value: Nil })\na rc count after b creation = 2\nb initial rc count = 1\nb next item = Some(RefCell { value: Cons(5, RefCell { value: Nil }) })\nb rc count after changing a = 2\na rc count after changing a = 2 aのリストをbを指すように変更した後のaとbのRcインスタンスの参照カウントは2です。 mainの終端で、コンパイラはまずbをドロップしようとし、aとbの各Rcインスタンスのカウントを1減らします。 しかしながら、それでもaはbにあったRcを参照しているので、そのRcのカウントは0ではなく1になり、 そのRcがヒープに確保していたメモリはドロップされません。メモリはただ、カウント1のままそこに永遠に居座るのです。 この循環参照を可視化するために、図15-4に図式を作成しました: 図15-4: お互いを指すリストaとbの循環参照 最後のprintln!のコメントを外してプログラムを実行したら、aがbを指して、bがaを指してと、 スタックがオーバーフローするまでコンパイラはこの循環を出力しようとするでしょう。 この場合、循環参照を作る直後にプログラムは終了します。この循環の結果は、それほど悲壮なものではありません。しかしながら、 より複雑なプログラムが多くのメモリを循環で確保し長い間その状態を保ったら、プログラムは必要以上のメモリを使用し、 使用可能なメモリを枯渇させてシステムを参らせてしまう可能性があります。 循環参照は簡単にできることではありませんが、不可能というわけでもありません。 Rc値を含むRefCell値があるなどの内部可変性と参照カウントのある型がネストして組み合わさっていたら、 循環していないことを保証しなければなりません; コンパイラがそれを捕捉することを信頼できないのです。 循環参照をするのは、自動テストやコードレビューなどの他のソフトウェア開発手段を使用して最小化すべきプログラム上のロジックバグでしょう。 循環参照を回避する別の解決策は、ある参照は所有権を表現して他の参照はしないというようにデータ構造を再構成することです。 結果として、所有権のある関係と所有権のない関係からなる循環ができ、所有権のある関係だけが、値がドロップされうるかどうかに影響します。 リスト15-25では、常にCons列挙子にリストを所有してほしいので、データ構造を再構成することはできません。 親ノードと子ノードからなるグラフを使った例に目を向けて、どんな時に所有権のない関係が循環参照を回避するのに適切な方法になるか確認しましょう。","breadcrumbs":"スマートポインタ » 循環参照させる","id":"286","title":"循環参照させる"},"287":{"body":"ここまで、Rc::cloneを呼び出すとRcインスタンスのstrong_countが増えることと、 strong_countが0になった時にRcインスタンスは片付けられることをデモしてきました。 Rc::downgradeを呼び出し、Rcへの参照を渡すことで、Rcインスタンス内部の値への 弱い参照 (weak reference)を作ることもできます。 Rc::downgradeを呼び出すと、型Weakのスマートポインタが得られます。 Rcインスタンスのstrong_countを1増やす代わりに、Rc::downgradeを呼び出すと、weak_countが1増えます。 strong_count同様、Rc型はweak_countを使用して、幾つのWeak参照が存在しているかを追跡します。 違いは、Rcが片付けられるのに、weak_countが0である必要はないということです。 強い参照は、Rcインスタンスの所有権を共有する方法です。弱い参照は、所有権関係を表現しません。 ひとたび、関係する値の強い参照カウントが0になれば、弱い参照が関わる循環はなんでも破壊されるので、 循環参照にはなりません。 Weakが参照する値はドロップされてしまっている可能性があるので、Weakが指す値に何かをするには、 値がまだ存在することを確認しなければなりません。Weakのupgradeメソッドを呼び出すことでこれをしてください。 このメソッドはOption>を返します。Rc値がまだドロップされていなければ、Someの結果が、 Rc値がドロップ済みなら、Noneの結果が得られます。upgradeがOptionを返すので、 コンパイラは、SomeケースとNoneケースが扱われていることを確かめてくれ、無効なポインタは存在しません。 例として、要素が次の要素を知っているだけのリストを使うのではなく、要素が子要素 と 親要素を知っている木を作りましょう。 木データ構造を作る: 子ノードのあるNode 手始めに子ノードを知っているノードのある木を構成します。独自のi32値と子供のNode値への参照を抱えるNodeという構造体を作ります: ファイル名: src/main.rs use std::rc::Rc;\nuse std::cell::RefCell; #[derive(Debug)]\nstruct Node { value: i32, children: RefCell>>,\n} Nodeに子供を所有してほしく、木の各Nodeに直接アクセスできるよう、その所有権を変数と共有したいです。 こうするために、Vec要素を型Rcの値になるよう定義しています。どのノードが他のノードの子供になるかも変更したいので、 Vec>の周りのchildrenをRefCellにしています。 次にこの構造体定義を使って値3と子供なしのleafという1つのNodeインスタンスと、 値5とleafを子要素の一つとして持つbranchという別のインスタンスを作成します。 リスト15-27のようにですね: ファイル名: src/main.rs # use std::rc::Rc;\n# use std::cell::RefCell;\n#\n# #[derive(Debug)]\n# struct Node {\n# value: i32,\n# children: RefCell>>,\n# }\n#\nfn main() { let leaf = Rc::new(Node { value: 3, children: RefCell::new(vec![]), }); let branch = Rc::new(Node { value: 5, children: RefCell::new(vec![Rc::clone(&leaf)]), });\n} リスト15-27: 子供なしのleafノードとleafを子要素に持つbranchノードを作る leafのRcをクローンし、branchに格納しているので、leafのNodeはleafとbranchという2つの所有者を持つことになります。 branch.childrenを通してbranchからleafへ辿ることはできるものの、leafからbranchへ辿る方法はありません。 理由は、leafにはbranchへの参照がなく、関係していることを知らないからです。leafにbranchが親であることを知ってほしいです。 次はそれを行います。 子供から親に参照を追加する 子供に親の存在を気付かせるために、Node構造体定義にparentフィールドを追加する必要があります。 parentの型を決める際に困ったことになります。Rcを含むことができないのはわかります。 そうしたら、leaf.parentがbranchを指し、branch.childrenがleafを指して循環参照になり、 strong_count値が絶対に0にならなくなってしまうからです。 この関係を別の方法で捉えると、親ノードは子供を所有すべきです: 親ノードがドロップされたら、 子ノードもドロップされるべきなのです。ですが、子供は親を所有するべきではありません: 子ノードをドロップしても、親はまだ存在するべきです。弱い参照を使う場面ですね! 従って、Rcの代わりにparentの型をWeakを使ったもの、具体的にはRefCell>にします。 さあ、Node構造体定義はこんな見た目になりました: ファイル名: src/main.rs use std::rc::{Rc, Weak};\nuse std::cell::RefCell; #[derive(Debug)]\nstruct Node { value: i32, parent: RefCell>, children: RefCell>>,\n} ノードは親ノードを参照できるものの、所有はしないでしょう。リスト15-28で、 leafノードが親のbranchを参照できるよう、この新しい定義を使用するようにmainを更新します: ファイル名: src/main.rs # use std::rc::{Rc, Weak};\n# use std::cell::RefCell;\n#\n# #[derive(Debug)]\n# struct Node {\n# value: i32,\n# parent: RefCell>,\n# children: RefCell>>,\n# }\n#\nfn main() { let leaf = Rc::new(Node { value: 3, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![]), }); // leafの親 = {:?} println!(\"leaf parent = {:?}\", leaf.parent.borrow().upgrade()); let branch = Rc::new(Node { value: 5, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![Rc::clone(&leaf)]), }); *leaf.parent.borrow_mut() = Rc::downgrade(&branch); println!(\"leaf parent = {:?}\", leaf.parent.borrow().upgrade());\n} リスト15-28: 親ノードのbranchへの弱い参照があるleafノード leafノードを作成することは、parentフィールドの例外を除いてリスト15-27でのleafノードの作成法の見た目に似ています: leafは親なしで始まるので、新しく空のWeak参照インスタンスを作ります。 この時点でupgradeメソッドを使用してleafの親への参照を得ようとすると、None値になります。 このことは、最初のprintln!文の出力でわかります: leaf parent = None branchノードを作る際、branchには親ノードがないので、こちらもparentフィールドには新しいWeak参照が入ります。 それでも、leafはbranchの子供になっています。一旦branchにNodeインスタンスができたら、 leafを変更して親へのWeak参照を与えることができます。leafのparentフィールドには、 RefCell>のborrow_mutメソッドを使用して、それからRc::downgrade関数を使用して、 branchのRcからbranchへのWeak参照を作ります。 再度leafの親を出力すると、今度はbranchを保持するSome列挙子が得られます: これでleafが親にアクセスできるようになったのです! leafを出力すると、リスト15-26で起こっていたような最終的にスタックオーバーフローに行き着く循環を避けることもできます; Weak参照は、(Weak)と出力されます: leaf parent = Some(Node { value: 5, parent: RefCell { value: (Weak) },\nchildren: RefCell { value: [Node { value: 3, parent: RefCell { value: (Weak) },\nchildren: RefCell { value: [] } }] } }) 無限の出力が欠けているということは、このコードは循環参照しないことを示唆します。 このことは、Rc::strong_countとRc::weak_countを呼び出すことで得られる値を見てもわかります。 strong_countとweak_countへの変更を可視化する 新しい内部スコープを作り、branchの作成をそのスコープに移動することで、 Rcインスタンスのstrong_countとweak_count値がどう変化するかを眺めましょう。 そうすることで、branchが作成され、それからスコープを抜けてドロップされる時に起こることが確認できます。 変更は、リスト15-29に示してあります: ファイル名: src/main.rs # use std::rc::{Rc, Weak};\n# use std::cell::RefCell;\n#\n# #[derive(Debug)]\n# struct Node {\n# value: i32,\n# parent: RefCell>,\n# children: RefCell>>,\n# }\n#\nfn main() { let leaf = Rc::new(Node { value: 3, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![]), }); println!( // leafのstrong_count = {}, weak_count = {} \"leaf strong = {}, weak = {}\", Rc::strong_count(&leaf), Rc::weak_count(&leaf), ); { let branch = Rc::new(Node { value: 5, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![Rc::clone(&leaf)]), }); *leaf.parent.borrow_mut() = Rc::downgrade(&branch); println!( // branchのstrong_count = {}, weak_count = {} \"branch strong = {}, weak = {}\", Rc::strong_count(&branch), Rc::weak_count(&branch), ); println!( \"leaf strong = {}, weak = {}\", Rc::strong_count(&leaf), Rc::weak_count(&leaf), ); } println!(\"leaf parent = {:?}\", leaf.parent.borrow().upgrade()); println!( \"leaf strong = {}, weak = {}\", Rc::strong_count(&leaf), Rc::weak_count(&leaf), );\n} リスト15-29: 内側のスコープでbranchを作成し、強弱参照カウントを調査する leaf作成後、そのRcの強カウントは1、弱カウントは0になります。内側のスコープでbranchを作成し、 leafに紐付け、この時点でカウントを出力すると、branchのRcの強カウントは1、 弱カウントも1になります(leaf.parentがWeakでbranchを指しているため)。 leafのカウントを出力すると、強カウントが2になっていることがわかります。branchが今は、 branch.childrenに格納されたleafのRcのクローンを持っているからですが、 それでも弱カウントは0でしょう。 内側のスコープが終わると、branchはスコープを抜け、Rcの強カウントは0に減るので、 このNodeはドロップされます。leaf.parentからの弱カウント1は、Nodeがドロップされるか否かには関係ないので、 メモリリークはしないのです! このスコープの終端以後にleafの親にアクセスしようとしたら、再びNoneが得られます。 プログラムの終端でleafのRcの強カウントは1、弱カウントは0です。 変数leafが今ではRcへの唯一の参照に再度なったからです。 カウントや値のドロップを管理するロジックは全て、RcやWeakとそのDropトレイトの実装に組み込まれています。 Nodeの定義で子供から親への関係はWeak参照になるべきと指定することで、 循環参照やメモリリークを引き起こさずに親ノードに子ノードを参照させたり、その逆を行うことができます。","breadcrumbs":"スマートポインタ » 循環参照を回避する: RcをWeakに変換する","id":"287","title":"循環参照を回避する: RcをWeakに変換する"},"288":{"body":"この章は、スマートポインタを使用してRustが既定で普通の参照に対して行うのと異なる保証や代償を行う方法を講義しました。 Box型は、既知のサイズで、ヒープに確保されたデータを指します。Rc型は、ヒープのデータへの参照の数を追跡するので、 データは複数の所有者を保有できます。内部可変性のあるRefCell型は、不変型が必要だけれども、 その型の中の値を変更する必要がある時に使用できる型を与えてくれます; また、コンパイル時ではなく実行時に借用規則を強制します。 DerefとDropトレイトについても議論しましたね。これらは、スマートポインタの多くの機能を可能にしてくれます。 メモリリークを引き起こす循環参照とWeakでそれを回避する方法も探究しました。 この章で興味をそそられ、独自のスマートポインタを実装したくなったら、もっと役に立つ情報を求めて、 “The Rustonomicon” をチェックしてください。 訳注: 日本語版のThe Rustonomiconは こちら です。 次は、Rustでの並行性について語ります。もういくつか新しいスマートポインタについてさえも学ぶでしょう。","breadcrumbs":"スマートポインタ » まとめ","id":"288","title":"まとめ"},"289":{"body":"並行性を安全かつ効率的に扱うことは、Rustの別の主な目標です。 並行プログラミング は、プログラムの異なる部分が独立して実行することであり、 並列プログラミング はプログラムの異なる部分が同時に実行することですが、多くのコンピュータが複数のプロセッサの利点を生かすようになるにつれ、 重要度を増しています。歴史的に、これらの文脈で行うプログラミングは困難で、エラーが起きやすいものでした: Rustはこれを変えると願っています。 当初、Rustチームは、メモリ安全性を保証することと、並行性問題を回避することは、 異なる方法で解決すべき別々の課題だと考えていました。時間とともに、チームは、所有権と型システムは、 メモリ安全性 と 並行性問題を管理する役に立つ一連の強力な道具であることを発見しました。 所有権と型チェックを活用することで、多くの並行性エラーは、実行時エラーではなくコンパイル時エラーになります。 故に、実行時に並行性のバグが起きた状況と全く同じ状況を再現しようと時間を浪費させるよりも、 不正なコードはコンパイルを拒み、問題を説明するエラーを提示するでしょう。結果として、 プロダクトになった後でなく、作業中にコードを修正できます。 Rustのこの方向性を 恐れるな!並行性 とニックネーム付けしました。これにより、潜在的なバグがなく、かつ、 新しいバグを導入することなく簡単にリファクタリングできるコードを書くことができます。 注釈: 簡潔性のため、並行または並列と述べることで正確を期するのではなく、 多くの問題を 並行 と割り切ってしまいます。この本がもし 並行性あるいは並列性 に関した本ならば、 詳述していたでしょう。この章に対しては、 並行 を使ったら、 脳内で 並行または並列 と置き換えてください。 多くの言語は、自分が提供する並行性問題を扱う解決策について独断的です。例えば、Erlangには、 メッセージ受け渡しの並行性に関する素晴らしい機能がありますが、スレッド間で状態を共有することに関しては、 曖昧な方法しかありません。可能な解決策の一部のみをサポートすることは、高級言語にとっては合理的な施策です。 なぜなら、高級言語は一部の制御を失う代わりに抽象化することから恩恵を受けるからです。ところが、 低級言語は、どんな場面でも最高のパフォーマンスで解決策を提供すると想定され、ハードウェアに関してほとんど抽象化はしません。 そのため、Rustは、自分の状況と必要性に適した方法が何であれ、問題をモデル化するためのいろんな道具を備えています。 こちらが、この章で講義する話題です: スレッドを生成して、複数のコードを同時に走らせる方法 チャンネルがスレッド間でメッセージを送る メッセージ受け渡し 並行性 複数のスレッドが何らかのデータにアクセスする 状態共有 並行性 標準ライブラリが提供する型だけでなく、ユーザが定義した型に対してもRustの並行性の安全保証を拡張するSyncとSendトレイト","breadcrumbs":"恐れるな!並行性","id":"289","title":"恐れるな!並行性"},"29":{"body":"単純なプロジェクトでは、Cargoは単にrustcを使用する以上の価値を生みませんが、プログラムが複雑になるにつれて、 その価値を証明するでしょう。複数のクレートからなる複雑なプロジェクトでは、Cargoにビルドを調整してもらうのが遥かに簡単です。 hello_cargoプロジェクトは単純ではありますが、今では、Rustのキャリアを通じて使用するであろう本物のツールを多く使用するようになりました。 事実、既存のどんなプロジェクトに取り組むにも、以下のコマンドを使用して、Gitでコードをチェックアウトし、 そのプロジェクトのディレクトリに移動し、ビルドできます: $ git clone someurl.com/someproject\n$ cd someproject\n$ cargo build Cargoについてより詳しく知るには、 ドキュメンテーション を確認してください。","breadcrumbs":"事始め » 習慣としてのCargo","id":"29","title":"習慣としてのCargo"},"290":{"body":"多くの現代のOSでは、実行中のプログラムのコードは プロセス で走り、OSは同時に複数のプロセスを管理します。 自分のプログラム内で、独立した部分を同時に実行できます。これらの独立した部分を走らせる機能を スレッド と呼びます。 プログラム内の計算を複数のスレッドに分けると、パフォーマンスが改善します。プログラムが同時に複数の作業をするからですが、 複雑度も増します。スレッドは同時に走らせることができるので、異なるスレッドのコードが走る順番に関して、 本来的に保証はありません。これは例えば以下のような問題を招きます: スレッドがデータやリソースに矛盾した順番でアクセスする競合状態 2つのスレッドがお互いにもう一方が持っているリソースを使用し終わるのを待ち、両者が継続するのを防ぐデッドロック 特定の状況でのみ起き、確実な再現や修正が困難なバグ Rustは、スレッドを使用する際の悪影響を軽減しようとしていますが、それでも、マルチスレッドの文脈でのプログラミングでは、 注意深い思考とシングルスレッドで走るプログラムとは異なるコード構造が必要です。 プログラミング言語によってスレッドはいくつかの方法で実装されています。多くのOSで、新規スレッドを生成するAPIが提供されています。 言語がOSのAPIを呼び出してスレッドを生成するこのモデルを時に 1:1 と呼び、1つのOSスレッドに対して1つの言語スレッドを意味します。 多くのプログラミング言語がスレッドの独自の特別な実装を提供しています。プログラミング言語が提供するスレッドは、 グリーン スレッドとして知られ、このグリーンスレッドを使用する言語は、それを異なる数のOSスレッドの文脈で実行します。 このため、グリーンスレッドのモデルは M:N モデルと呼ばれます: M個のグリーンスレッドに対して、 N個のOSスレッドがあり、MとNは必ずしも同じ数字ではありません。 各モデルには、それだけの利点と代償があり、Rustにとって最も重要な代償は、ランタイムのサポートです。 ランタイム は、混乱しやすい用語で文脈によって意味も変わります。 この文脈での ランタイム とは、言語によって全てのバイナリに含まれるコードのことを意味します。 言語によってこのコードの大小は決まりますが、非アセンブリ言語は全てある量の実行時コードを含みます。 そのため、口語的に誰かが「ノーランタイム」と言ったら、「小さいランタイム」のことを意味することがしばしばあります。 ランタイムが小さいと機能も少ないですが、バイナリのサイズも小さくなるという利点があり、 その言語を他の言語とより多くの文脈で組み合わせることが容易になります。多くの言語では、 より多くの機能と引き換えにランタイムのサイズが膨れ上がるのは、受け入れられることですが、 Rustにはほとんどゼロのランタイムが必要でパフォーマンスを維持するためにCコードを呼び出せることを妥協できないのです。 M:Nのグリーンスレッドモデルは、スレッドを管理するのにより大きな言語ランタイムが必要です。よって、 Rustの標準ライブラリは、1:1スレッドの実装のみを提供しています。Rustはそのような低級言語なので、 例えば、むしろどのスレッドがいつ走るかのより詳細な制御や、より低コストの文脈切り替えなどの一面をオーバーヘッドと引き換えるなら、 M:Nスレッドの実装をしたクレートもあります。 今やRustにおけるスレッドを定義したので、標準ライブラリで提供されているスレッド関連のAPIの使用法を探究しましょう。","breadcrumbs":"恐れるな!並行性 » スレッドを使用してコードを同時に走らせる","id":"290","title":"スレッドを使用してコードを同時に走らせる"},"291":{"body":"新規スレッドを生成するには、thread::spawn関数を呼び出し、 新規スレッドで走らせたいコードを含むクロージャ(クロージャについては第13章で語りました)を渡します。 リスト16-1の例は、メインスレッドと新規スレッドからテキストを出力します: ファイル名: src/main.rs use std::thread;\nuse std::time::Duration; fn main() { thread::spawn(|| { for i in 1..10 { // やあ!立ち上げたスレッドから数字{}だよ! println!(\"hi number {} from the spawned thread!\", i); thread::sleep(Duration::from_millis(1)); } }); for i in 1..5 { // メインスレッドから数字{}だよ! println!(\"hi number {} from the main thread!\", i); thread::sleep(Duration::from_millis(1)); }\n} リスト16-1: メインスレッドが別のものを出力する間に新規スレッドを生成して何かを出力する この関数では、新しいスレッドは、実行が終わったかどうかにかかわらず、メインスレッドが終了したら停止することに注意してください。 このプログラムからの出力は毎回少々異なる可能性がありますが、だいたい以下のような感じでしょう: hi number 1 from the main thread!\nhi number 1 from the spawned thread!\nhi number 2 from the main thread!\nhi number 2 from the spawned thread!\nhi number 3 from the main thread!\nhi number 3 from the spawned thread!\nhi number 4 from the main thread!\nhi number 4 from the spawned thread!\nhi number 5 from the spawned thread! thread::sleepを呼び出すと、少々の間、スレッドの実行を止め、違うスレッドを走らせることができます。 スレッドはおそらく切り替わるでしょうが、保証はありません: OSがスレッドのスケジュールを行う方法によります。 この実行では、コード上では立ち上げられたスレッドのprint文が先に現れているのに、メインスレッドが先に出力しています。また、 立ち上げたスレッドにはiが9になるまで出力するよう指示しているのに、メインスレッドが終了する前の5までしか到達していません。 このコードを実行してメインスレッドの出力しか目の当たりにできなかったり、オーバーラップがなければ、 範囲の値を増やしてOSがスレッド切り替えを行う機会を増やしてみてください。","breadcrumbs":"恐れるな!並行性 » spawnで新規スレッドを生成する","id":"291","title":"spawnで新規スレッドを生成する"},"292":{"body":"リスト16-1のコードは、メインスレッドが終了するためにほとんどの場合、立ち上げたスレッドがすべて実行されないだけでなく、 立ち上げたスレッドが実行されるかどうかも保証できません。原因は、スレッドの実行順に保証がないからです。 thread::spawnの戻り値を変数に保存することで、立ち上げたスレッドが実行されなかったり、 完全には実行されなかったりする問題を修正することができます。thread:spawnの戻り値の型はJoinHandleです。 JoinHandleは、そのjoinメソッドを呼び出したときにスレッドの終了を待つ所有された値です。 リスト16-2は、リスト16-1で生成したスレッドのJoinHandleを使用し、joinを呼び出して、 mainが終了する前に、立ち上げたスレッドが確実に完了する方法を示しています: ファイル名: src/main.rs use std::thread;\nuse std::time::Duration; fn main() { let handle = thread::spawn(|| { for i in 1..10 { println!(\"hi number {} from the spawned thread!\", i); thread::sleep(Duration::from_millis(1)); } }); for i in 1..5 { println!(\"hi number {} from the main thread!\", i); thread::sleep(Duration::from_millis(1)); } handle.join().unwrap();\n} リスト16-2: thread::spawnのJoinHandleを保存してスレッドが完了するのを保証する ハンドルに対してjoinを呼び出すと、ハンドルが表すスレッドが終了するまで現在実行中のスレッドをブロックします。 スレッドを ブロック するとは、そのスレッドが動いたり、終了したりすることを防ぐことです。 joinの呼び出しをメインスレッドのforループの後に配置したので、リスト16-2を実行すると、 以下のように出力されるはずです: hi number 1 from the main thread!\nhi number 2 from the main thread!\nhi number 1 from the spawned thread!\nhi number 3 from the main thread!\nhi number 2 from the spawned thread!\nhi number 4 from the main thread!\nhi number 3 from the spawned thread!\nhi number 4 from the spawned thread!\nhi number 5 from the spawned thread!\nhi number 6 from the spawned thread!\nhi number 7 from the spawned thread!\nhi number 8 from the spawned thread!\nhi number 9 from the spawned thread! 2つのスレッドが代わる代わる実行されていますが、handle.join()呼び出しのためにメインスレッドは待機し、 立ち上げたスレッドが終了するまで終わりません。 ですが、代わりにhandle.join()をforループの前に移動したらどうなるのか確認しましょう。こんな感じに: ファイル名: src/main.rs use std::thread;\nuse std::time::Duration; fn main() { let handle = thread::spawn(|| { for i in 1..10 { println!(\"hi number {} from the spawned thread!\", i); thread::sleep(Duration::from_millis(1)); } }); handle.join().unwrap(); for i in 1..5 { println!(\"hi number {} from the main thread!\", i); thread::sleep(Duration::from_millis(1)); }\n} メインスレッドは、立ち上げたスレッドが終了するまで待ち、それからforループを実行するので、 以下のように出力はもう混ざらないでしょう: hi number 1 from the spawned thread!\nhi number 2 from the spawned thread!\nhi number 3 from the spawned thread!\nhi number 4 from the spawned thread!\nhi number 5 from the spawned thread!\nhi number 6 from the spawned thread!\nhi number 7 from the spawned thread!\nhi number 8 from the spawned thread!\nhi number 9 from the spawned thread!\nhi number 1 from the main thread!\nhi number 2 from the main thread!\nhi number 3 from the main thread!\nhi number 4 from the main thread! どこでjoinを呼ぶかといったほんの些細なことが、スレッドが同時に走るかどうかに影響することもあります。","breadcrumbs":"恐れるな!並行性 » joinハンドルで全スレッドの終了を待つ","id":"292","title":"joinハンドルで全スレッドの終了を待つ"},"293":{"body":"moveクロージャは、thread::spawnとともによく使用されます。 あるスレッドのデータを別のスレッドで使用できるようになるからです。 第13章で、クロージャの引数リストの前にmoveキーワードを使用して、 クロージャに環境で使用している値の所有権を強制的に奪わせることができると述べました。 このテクニックは、あるスレッドから別のスレッドに値の所有権を移すために新しいスレッドを生成する際に特に有用です。 リスト16-1において、thread::spawnに渡したクロージャには引数がなかったことに注目してください: 立ち上げたスレッドのコードでメインスレッドからのデータは何も使用していないのです。 立ち上げたスレッドでメインスレッドのデータを使用するには、立ち上げるスレッドのクロージャは、 必要な値をキャプチャしなければなりません。リスト16-3は、メインスレッドでベクタを生成し、 立ち上げたスレッドで使用する試みを示しています。しかしながら、すぐにわかるように、これはまだ動きません: ファイル名: src/main.rs use std::thread; fn main() { let v = vec![1, 2, 3]; let handle = thread::spawn(|| { // こちらがベクタ: {:?} println!(\"Here's a vector: {:?}\", v); }); handle.join().unwrap();\n} リスト16-3: 別のスレッドでメインスレッドが生成したベクタを使用しようとする クロージャはvを使用しているので、vをキャプチャし、クロージャの環境の一部にしています。 thread::spawnはこのクロージャを新しいスレッドで走らせるので、 その新しいスレッド内でvにアクセスできるはずです。しかし、このコードをコンパイルすると、 以下のようなエラーが出ます: error[E0373]: closure may outlive the current function, but it borrows `v`,\nwhich is owned by the current function\n(エラー: クロージャは現在の関数よりも長生きするかもしれませんが、現在の関数が所有している\n`v`を借用しています) --> src/main.rs:6:32 |\n6 | let handle = thread::spawn(|| { | ^^ may outlive borrowed value `v`\n7 | println!(\"Here's a vector: {:?}\", v); | - `v` is borrowed here |\nhelp: to force the closure to take ownership of `v` (and any other referenced\nvariables), use the `move` keyword\n(助言: `v`(や他の参照されている変数)の所有権をクロージャに奪わせるには、`move`キーワードを使用してください) |\n6 | let handle = thread::spawn(move || { | ^^^^^^^ Rustはvのキャプチャ方法を 推論 し、println!はvへの参照のみを必要とするので、クロージャは、 vを借用しようとします。ですが、問題があります: コンパイラには、立ち上げたスレッドがどのくらいの期間走るのかわからないので、 vへの参照が常に有効であるか把握できないのです。 リスト16-4は、vへの参照がより有効でなさそうな筋書きです: ファイル名: src/main.rs use std::thread; fn main() { let v = vec![1, 2, 3]; let handle = thread::spawn(|| { println!(\"Here's a vector: {:?}\", v); }); // いや〜! drop(v); // oh no! handle.join().unwrap();\n} リスト16-4: vをドロップするメインスレッドからvへの参照をキャプチャしようとするクロージャを伴うスレッド このコードを実行できてしまうなら、立ち上げたスレッドはまったく実行されることなく即座にバックグラウンドに置かれる可能性があります。 立ち上げたスレッドは内部にvへの参照を保持していますが、メインスレッドは、第15章で議論したdrop関数を使用して、 即座にvをドロップしています。そして、立ち上げたスレッドが実行を開始する時には、vはもう有効ではなく、 参照も不正になるのです。あちゃー! リスト16-3のコンパイルエラーを修正するには、エラーメッセージのアドバイスを活用できます: help: to force the closure to take ownership of `v` (and any other referenced\nvariables), use the `move` keyword |\n6 | let handle = thread::spawn(move || { | ^^^^^^^ クロージャの前にmoveキーワードを付することで、コンパイラに値を借用すべきと推論させるのではなく、 クロージャに使用している値の所有権を強制的に奪わせます。リスト16-5に示したリスト16-3に対する変更は、 コンパイルでき、意図通りに動きます: ファイル名: src/main.rs use std::thread; fn main() { let v = vec![1, 2, 3]; let handle = thread::spawn(move || { println!(\"Here's a vector: {:?}\", v); }); handle.join().unwrap();\n} リスト16-5: moveキーワードを使用してクロージャに使用している値の所有権を強制的に奪わせる moveクロージャを使用していたら、メインスレッドがdropを呼び出すリスト16-4のコードはどうなるのでしょうか? moveで解決するのでしょうか?残念ながら、違います; リスト16-4が試みていることは別の理由によりできないので、 違うエラーが出ます。クロージャにmoveを付与したら、vをクロージャの環境にムーブするので、 最早メインスレッドでdropを呼び出すことは叶わなくなるでしょう。代わりにこのようなコンパイルエラーが出るでしょう: error[E0382]: use of moved value: `v`\n(エラー: ムーブされた値の使用: `v`) --> src/main.rs:10:10 |\n6 | let handle = thread::spawn(move || { | ------- value moved (into closure) here\n...\n10 | drop(v); // oh no! | ^ value used here after move | = note: move occurs because `v` has type `std::vec::Vec`, which does not implement the `Copy` trait (注釈: `v`の型が`std::vec::Vec`のためムーブが起きました。この型は、`Copy`トレイトを実装していません) 再三Rustの所有権規則が救ってくれました!リスト16-3のコードはエラーになりました。 コンパイラが一時的に保守的になり、スレッドに対してvを借用しただけだったからで、 これは、メインスレッドは理論上、立ち上げたスレッドの参照を不正化する可能性があることを意味します。 vの所有権を立ち上げたスレッドに移動するとコンパイラに指示することで、 メインスレッドはもうvを使用しないとコンパイラに保証しているのです。リスト16-4も同様に変更したら、 メインスレッドでvを使用しようとする際に所有権の規則に違反することになります。 moveキーワードにより、Rustの保守的な借用のデフォルトが上書きされるのです; 所有権の規則を侵害させてくれないのです。 スレッドとスレッドAPIの基礎知識を得たので、スレッドで できる ことを見ていきましょう。","breadcrumbs":"恐れるな!並行性 » スレッドでmoveクロージャを使用する","id":"293","title":"スレッドでmoveクロージャを使用する"},"294":{"body":"人気度を増してきている安全な並行性を保証する一つのアプローチが メッセージ受け渡し で、 スレッドやアクターがデータを含むメッセージを相互に送り合うことでやり取りします。 こちらが、 Go言語のドキュメンテーション のスローガンにある考えです: 「メモリを共有することでやり取りするな; 代わりにやり取りすることでメモリを共有しろ」 メッセージ送信並行性を達成するためにRustに存在する一つの主な道具は、 チャンネル で、 Rustの標準ライブラリが実装を提供しているプログラミング概念です。プログラミングのチャンネルは、 水の流れのように考えることができます。小川とか川ですね。アヒルのおもちゃやボートみたいなものを流れに置いたら、 水路の終端まで下流に流れていきます。 プログラミングにおけるチャンネルは、2分割できます: 転送機と受信機です。転送機はアヒルのおもちゃを川に置く上流になり、 受信機は、アヒルのおもちゃが行き着く下流になります。コードのある箇所が送信したいデータとともに転送機のメソッドを呼び出し、 別の部分がメッセージが到着していないか受信側を調べます。転送機と受信機のどちらかがドロップされると、 チャンネルは 閉じられた と言います。 ここで、1つのスレッドが値を生成し、それをチャンネルに送信し、別のスレッドがその値を受け取り、 出力するプログラムに取り掛かります。チャンネルを使用してスレッド間に単純な値を送り、 機能の説明を行います。一旦、そのテクニックに慣れてしまえば、チャンネルを使用してチャットシステムや、 多くのスレッドが計算の一部を担い、結果をまとめる1つのスレッドにその部分を送るようなシステムを実装できるでしょう。 まず、リスト16-6において、チャンネルを生成するものの、何もしません。 チャンネル越しにどんな型の値を送りたいのかコンパイラがわからないため、 これはまだコンパイルできないことに注意してください。 ファイル名: src/main.rs use std::sync::mpsc; fn main() { let (tx, rx) = mpsc::channel();\n# tx.send(()).unwrap();\n} リスト16-6: チャンネルを生成し、2つの部品をtxとrxに代入する mpsc::channel関数で新しいチャンネルを生成しています; mpscは multiple producer, single consumer を表しています。 簡潔に言えば、Rustの標準ライブラリがチャンネルを実装している方法は、1つのチャンネルが値を生成する複数の 送信 側と、 その値を消費するたった1つの 受信 側を持つことができるということを意味します。 複数の小川が互いに合わさって1つの大きな川になるところを想像してください: どの小川を通っても、送られたものは最終的に1つの川に行き着きます。今は、1つの生成器から始めますが、 この例が動作するようになったら、複数の生成器を追加します。 mpsc::channel関数はタプルを返し、1つ目の要素は、送信側、2つ目の要素は受信側になります。 txとrxという略称は、多くの分野で伝統的に 転送機 と 受信機 にそれぞれ使用されているので、 変数をそのように名付けて、各終端を示します。タプルを分配するパターンを伴うlet文を使用しています; let文でパターンを使用することと分配については、第18章で議論しましょう。このようにlet文を使うと、 mpsc::channelで返ってくるタプルの部品を抽出するのが便利になります。 立ち上げたスレッドがメインスレッドとやり取りするように、転送機を立ち上げたスレッドに移動し、 1文字列を送らせましょう。リスト16-7のようにですね。川の上流にアヒルのおもちゃを置いたり、 チャットのメッセージをあるスレッドから別のスレッドに送るみたいですね。 ファイル名: src/main.rs use std::thread;\nuse std::sync::mpsc; fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let val = String::from(\"hi\"); tx.send(val).unwrap(); });\n} リスト16-7: txを立ち上げたスレッドに移動し、「やあ」を送る 今回も、thread::spawnを使用して新しいスレッドを生成し、それからmoveを使用して、 立ち上げたスレッドがtxを所有するようにクロージャにtxをムーブしています。立ち上げたスレッドは、 メッセージをチャンネルを通して送信できるように、チャンネルの送信側を所有する必要があります。 転送側には、送信したい値を取るsendメソッドがあります。sendメソッドはResult型を返すので、 既に受信側がドロップされ、値を送信する場所がなければ、送信処理はエラーを返します。 この例では、エラーの場合には、パニックするようにunwrapを呼び出しています。ですが、実際のアプリケーションでは、 ちゃんと扱うでしょう: 第9章に戻ってちゃんとしたエラー処理の方法を再確認してください。 リスト16-8において、メインスレッドのチャンネルの受信側から値を得ます。 アヒルのおもちゃを川の終端で水から回収したり、チャットメッセージを取得するみたいですね。 ファイル名: src/main.rs use std::thread;\nuse std::sync::mpsc; fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let val = String::from(\"hi\"); tx.send(val).unwrap(); }); let received = rx.recv().unwrap(); // 値は{}です println!(\"Got: {}\", received);\n} リスト16-8: 「やあ」の値をメインスレッドで受け取り、出力する チャンネルの受信側には有用なメソッドが2つあります: recvとtry_recvです。 receive の省略形であるrecvを使っています。これは、メインスレッドの実行をブロックし、 値がチャンネルを流れてくるまで待機します。一旦値が送信されたら、recvはそれをResultに含んで返します。 チャンネルの送信側が閉じたら、recvはエラーを返し、もう値は来ないと通知します。 try_recvメソッドはブロックせず、代わりに即座にResultを返します: メッセージがあったら、それを含むOk値、今回は何もメッセージがなければ、Err値です。 メッセージを待つ間にこのスレッドにすることが他にあれば、try_recvは有用です: try_recvを頻繁に呼び出し、メッセージがあったら処理し、それ以外の場合は、 再度チェックするまでちょっとの間、他の作業をするループを書くことができるでしょう。 この例では、簡潔性のためにrecvを使用しました; メッセージを待つこと以外にメインスレッドがすべき作業はないので、 メインスレッドをブロックするのは適切です。 リスト16-8のコードを実行したら、メインスレッドから値が出力されるところを目撃するでしょう: Got: hi 完璧です!","breadcrumbs":"恐れるな!並行性 » メッセージ受け渡しを使ってスレッド間でデータを転送する","id":"294","title":"メッセージ受け渡しを使ってスレッド間でデータを転送する"},"295":{"body":"安全な並行コードを書く手助けをしてくれるので、所有権規則は、メッセージ送信で重要な役割を担っています。 並行プログラミングでエラーを回避することは、Rustプログラム全体で所有権について考える利点です。 実験をしてチャンネルと所有権がともに動いて、どう問題を回避するかをお見せしましょう: val値を立ち上げたスレッドで、チャンネルに送った 後 に使用を試みます。 リスト16-9のコードのコンパイルを試みて、このコードが許容されない理由を確認してください: ファイル名: src/main.rs use std::thread;\nuse std::sync::mpsc; fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let val = String::from(\"hi\"); tx.send(val).unwrap(); // valは{} println!(\"val is {}\", val); }); let received = rx.recv().unwrap(); println!(\"Got: {}\", received);\n} リスト16-9: チャンネルに送信後にvalの使用を試みる ここで、tx.send経由でチャンネルに送信後にvalを出力しようとしています。これを許可するのは、悪い考えです: 一旦、値が他のスレッドに送信されたら、再度値を使用しようとする前にそのスレッドが変更したりドロップできてしまいます。 可能性として、その別のスレッドの変更により、矛盾していたり存在しないデータのせいでエラーが発生したり、 予期しない結果になるでしょう。ですが、リスト16-9のコードのコンパイルを試みると、Rustはエラーを返します: error[E0382]: use of moved value: `val` --> src/main.rs:10:31 |\n9 | tx.send(val).unwrap(); | --- value moved here\n10 | println!(\"val is {}\", val); | ^^^ value used here after move | = note: move occurs because `val` has type `std::string::String`, which does\nnot implement the `Copy` trait 並行性のミスがコンパイルエラーを招きました。send関数は引数の所有権を奪い、 値がムーブされると、受信側が所有権を得るのです。これにより、送信後に誤って再度値を使用するのを防いでくれます; 所有権システムが、万事問題ないことを確認してくれます。","breadcrumbs":"恐れるな!並行性 » チャンネルと所有権の転送","id":"295","title":"チャンネルと所有権の転送"},"296":{"body":"リスト16-8のコードはコンパイルでき、動きましたが、2つの個別のスレッドがお互いにチャンネル越しに会話していることは、 明瞭に示されませんでした。リスト16-10において、リスト16-8のコードが並行に動いていることを証明する変更を行いました: 立ち上げたスレッドは、複数のメッセージを送信し、各メッセージ間で、1秒待機します。 ファイル名: src/main.rs use std::thread;\nuse std::sync::mpsc;\nuse std::time::Duration; fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { // スレッドからやあ(hi from the thread) let vals = vec![ String::from(\"hi\"), String::from(\"from\"), String::from(\"the\"), String::from(\"thread\"), ]; for val in vals { tx.send(val).unwrap(); thread::sleep(Duration::from_secs(1)); } }); for received in rx { println!(\"Got: {}\", received); }\n} リスト16-10: 複数のメッセージを送信し、メッセージ間で停止する 今回は、メインスレッドに送信したい文字列のベクタを立ち上げたスレッドが持っています。 それらを繰り返し、各々個別に送信し、Durationの値1秒とともにthread::sleep関数を呼び出すことで、 メッセージ間で停止します。 メインスレッドにおいて、最早recv関数を明示的に呼んではいません: 代わりに、 rxをイテレータとして扱っています。受信した値それぞれを出力します。 チャンネルが閉じられると、繰り返しも終わります。 リスト16-10のコードを走らせると、各行の間に1秒の待機をしつつ、以下のような出力を目の当たりにするはずです: Got: hi\nGot: from\nGot: the\nGot: thread メインスレッドのforループには停止したり、遅れせたりするコードは何もないので、 メインスレッドが立ち上げたスレッドから値を受け取るのを待機していることがわかります。","breadcrumbs":"恐れるな!並行性 » 複数の値を送信し、受信側が待機するのを確かめる","id":"296","title":"複数の値を送信し、受信側が待機するのを確かめる"},"297":{"body":"mpscは、 mutiple producer, single consumer の頭字語であると前述しました。 mpscを使い、リスト16-10のコードを拡張して、全ての値を同じ受信機に送信する複数のスレッドを生成しましょう。 チャンネルの転送の片割れをクローンすることでそうすることができます。リスト16-11のようにですね: ファイル名: src/main.rs # use std::thread;\n# use std::sync::mpsc;\n# use std::time::Duration;\n#\n# fn main() {\n// --snip-- let (tx, rx) = mpsc::channel(); let tx1 = mpsc::Sender::clone(&tx);\nthread::spawn(move || { let vals = vec![ String::from(\"hi\"), String::from(\"from\"), String::from(\"the\"), String::from(\"thread\"), ]; for val in vals { tx1.send(val).unwrap(); thread::sleep(Duration::from_secs(1)); }\n}); thread::spawn(move || { // 君のためにもっとメッセージを(more messages for you) let vals = vec![ String::from(\"more\"), String::from(\"messages\"), String::from(\"for\"), String::from(\"you\"), ]; for val in vals { tx.send(val).unwrap(); thread::sleep(Duration::from_secs(1)); }\n}); for received in rx { println!(\"Got: {}\", received);\n} // --snip--\n# } リスト16-11: 複数の生成器から複数のメッセージを送信する 今回、最初のスレッドを立ち上げる前に、チャンネルの送信側に対してcloneを呼び出しています。 これにより、最初に立ち上げたスレッドに渡せる新しい送信ハンドルが得られます。 元のチャンネルの送信側は、2番目に立ち上げたスレッドに渡します。これにより2つスレッドが得られ、 それぞれチャンネルの受信側に異なるメッセージを送信します。 コードを実行すると、出力は以下のようなものになるはずです: Got: hi\nGot: more\nGot: from\nGot: messages\nGot: for\nGot: the\nGot: thread\nGot: you 別の順番で値が出る可能性もあります; システム次第です。並行性が面白いと同時に難しい部分でもあります。 異なるスレッドで色々な値を与えてthread::sleepで実験をしたら、走らせるたびにより非決定的になり、 毎回異なる出力をするでしょう。 チャンネルの動作方法を見たので、他の並行性に目を向けましょう。","breadcrumbs":"恐れるな!並行性 » 転送機をクローンして複数の生成器を作成する","id":"297","title":"転送機をクローンして複数の生成器を作成する"},"298":{"body":"メッセージ受け渡しは、並行性を扱う素晴らしい方法ですが、唯一の方法ではありません。 Go言語ドキュメンテーションのスローガンのこの部分を再び考えてください: 「メモリを共有することでやり取りする。」 メモリを共有することでやり取りするとはどんな感じなのでしょうか?さらに、 なぜメッセージ受け渡しに熱狂的な人は、それを使わず、代わりに全く反対のことをするのでしょうか? ある意味では、どんなプログラミング言語のチャンネルも単独の所有権に類似しています。 一旦チャンネルに値を転送したら、その値は最早使用することがないからです。 メモリ共有並行性は、複数の所有権に似ています: 複数のスレッドが同時に同じメモリ位置にアクセスできるのです。 第15章でスマートポインタが複数の所有権を可能にするのを目の当たりにしたように、 異なる所有者を管理する必要があるので、複数の所有権は複雑度を増させます。 Rustの型システムと所有権規則は、この管理を正しく行う大きな助けになります。 例として、メモリ共有を行うより一般的な並行性の基本型の一つであるミューテックスを見てみましょう。","breadcrumbs":"恐れるな!並行性 » 状態共有並行性","id":"298","title":"状態共有並行性"},"299":{"body":"ミューテックスは、どんな時も1つのスレッドにしかなんらかのデータへのアクセスを許可しないというように、 \"mutual exclusion\"(相互排他)の省略形です。ミューテックスにあるデータにアクセスするには、 ミューテックスのロックを所望することでアクセスしたいことをまず、スレッドは通知しなければなりません。 ロックとは、現在誰がデータへの排他的アクセスを行なっているかを追跡するミューテックの一部をなすデータ構造です。 故に、ミューテックスはロックシステム経由で保持しているデータを 死守する (guarding)と解説されます。 ミューテックスは、2つの規則を覚えておく必要があるため、難しいという評判があります: データを使用する前にロックの獲得を試みなければならない。 ミューテックスが死守しているデータの使用が終わったら、他のスレッドがロックを獲得できるように、 データをアンロックしなければならない。 ミューテックスを現実世界の物で例えるなら、マイクが1つしかない会議のパネルディスカッションを思い浮かべてください。 パネリストが発言できる前に、マイクを使用したいと申し出たり、通知しなければなりません。マイクを受け取ったら、 話したいだけ話し、それから次に発言を申し出たパネリストにマイクを手渡します。パネリストが発言し終わった時に、 マイクを手渡すのを忘れていたら、誰も他の人は発言できません。共有されているマイクの管理がうまくいかなければ、 パネルは予定通りに機能しないでしょう! ミューテックスの管理は、正しく行うのに著しく技工を要することがあるので、多くの人がチャンネルに熱狂的になるわけです。 しかしながら、Rustの型システムと所有権規則のおかげで、ロックとアンロックをおかしくすることはありません。 MutexのAPI ミューテックスの使用方法の例として、ミューテックスをシングルスレッドの文脈で使うことから始めましょう。 リスト16-12のようにですね: ファイル名: src/main.rs use std::sync::Mutex; fn main() { let m = Mutex::new(5); { let mut num = m.lock().unwrap(); *num = 6; } println!(\"m = {:?}\", m);\n} リスト16-12: 簡潔性のためにMutexのAPIをシングルスレッドの文脈で探究する 多くの型同様、newという関連関数を使用してMutexを生成します。ミューテックス内部のデータにアクセスするには、 lockメソッドを使用してロックを獲得します。この呼び出しは、現在のスレッドをブロックするので、 ロックを得られる順番が来るまで何も作業はできません。 ロックを保持している他のスレッドがパニックしたら、lockの呼び出しは失敗するでしょう。その場合、 誰もロックを取得することは叶わないので、unwrapすると決定し、そのような状況になったら、 このスレッドをパニックさせます。 ロックを獲得した後、今回の場合、numと名付けられていますが、戻り値を中に入っているデータへの可変参照として扱うことができます。 型システムにより、mの値を使用する前にロックを獲得していることが確認されます: Mutexはi32ではないので、 i32を使用できるようにするには、ロックを獲得し なければならない のです。忘れることはあり得ません; 型システムにより、それ以外の場合に内部のi32にアクセスすることは許されません。 お察しかもしれませんが、Mutexはスマートポインタです。より正確を期すなら、 lockの呼び出しがMutexGuardというスマートポインタを 返却 します。このスマートポインタが、 内部のデータを指すDerefを実装しています; このスマートポインタはさらにMutexGuardがスコープを外れた時に、 自動的にロックを解除するDrop実装もしていて、これがリスト16-12の内部スコープの終わりで発生します。 結果として、ロックの解除が自動的に行われるので、ロックの解除を忘れ、 ミューテックスが他のスレッドで使用されるのを阻害するリスクを負いません。 ロックをドロップした後、ミューテックスの値を出力し、内部のi32の値を6に変更できたことが確かめられるのです。 複数のスレッド間でMutexを共有する さて、Mutexを使って複数のスレッド間で値を共有してみましょう。10個のスレッドを立ち上げ、 各々カウンタの値を1ずつインクリメントさせるので、カウンタは0から10まで上がります。 以下の数例は、コンパイルエラーになることに注意し、そのエラーを使用してMutexの使用法と、 コンパイラがそれを正しく活用する手助けをしてくれる方法について学びます。リスト16-13が最初の例です: ファイル名: src/main.rs use std::sync::Mutex;\nuse std::thread; fn main() { let counter = Mutex::new(0); let mut handles = vec![]; for _ in 0..10 { let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } println!(\"Result: {}\", *counter.lock().unwrap());\n} リスト16-13: Mutexにより死守されているカウンタを10個のスレッドがそれぞれインクリメントする リスト16-12のように、counter変数を生成してMutexの内部にi32を保持しています。 次に、数値の範囲をマッピングして10個のスレッドを生成しています。thread::spawnを使用して、 全スレッドに同じクロージャを与えています。このクロージャは、スレッド内にカウンタをムーブし、 lockメソッドを呼ぶことでMutexのロックを獲得し、それからミューテックスの値に1を足します。 スレッドがクロージャを実行し終わったら、numはスコープ外に出てロックを解除するので、 他のスレッドが獲得できるわけです。 メインスレッドで全てのjoinハンドルを収集します。それからリスト16-2のように、各々に対してjoinを呼び出し、 全スレッドが終了するのを確かめています。その時点で、メインスレッドはロックを獲得し、このプログラムの結果を出力します。 この例はコンパイルできないでしょうと仄めかしました。では、理由を探りましょう! error[E0382]: capture of moved value: `counter`\n(エラー: ムーブされた値をキャプチャしています: `counter`) --> src/main.rs:10:27 |\n9 | let handle = thread::spawn(move || { | ------- value moved (into closure) here\n10 | let mut num = counter.lock().unwrap(); | ^^^^^^^ value captured here after move | = note: move occurs because `counter` has type `std::sync::Mutex`, which does not implement the `Copy` trait error[E0382]: use of moved value: `counter` --> src/main.rs:21:29 |\n9 | let handle = thread::spawn(move || { | ------- value moved (into closure) here\n...\n21 | println!(\"Result: {}\", *counter.lock().unwrap()); | ^^^^^^^ value used here after move | = note: move occurs because `counter` has type `std::sync::Mutex`, which does not implement the `Copy` trait error: aborting due to 2 previous errors\n(エラー: 前述の2つのエラーによりアボート) エラーメッセージは、counter値はクロージャにムーブされ、それからlockを呼び出したときにキャプチャされていると述べています。 その説明は、所望した動作のように聞こえますが、許可されていないのです! プログラムを単純化してこれを理解しましょう。forループで10個スレッドを生成する代わりに、 ループなしで2つのスレッドを作るだけにしてどうなるか確認しましょう。 リスト16-13の最初のforループを代わりにこのコードと置き換えてください: use std::sync::Mutex;\nuse std::thread; fn main() { let counter = Mutex::new(0); let mut handles = vec![]; let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); let handle2 = thread::spawn(move || { let mut num2 = counter.lock().unwrap(); *num2 += 1; }); handles.push(handle2); for handle in handles { handle.join().unwrap(); } println!(\"Result: {}\", *counter.lock().unwrap());\n} 2つのスレッドを生成し、2番目のスレッドの変数名をhandle2とnum2に変更しています。 今回このコードを走らせると、コンパイラは以下の出力をします: error[E0382]: capture of moved value: `counter` --> src/main.rs:16:24 |\n8 | let handle = thread::spawn(move || { | ------- value moved (into closure) here\n...\n16 | let mut num2 = counter.lock().unwrap(); | ^^^^^^^ value captured here after move | = note: move occurs because `counter` has type `std::sync::Mutex`, which does not implement the `Copy` trait error[E0382]: use of moved value: `counter` --> src/main.rs:26:29 |\n8 | let handle = thread::spawn(move || { | ------- value moved (into closure) here\n...\n26 | println!(\"Result: {}\", *counter.lock().unwrap()); | ^^^^^^^ value used here after move | = note: move occurs because `counter` has type `std::sync::Mutex`, which does not implement the `Copy` trait error: aborting due to 2 previous errors なるほど!最初のエラーメッセージは、handleに紐づけられたスレッドのクロージャにcounterがムーブされていることを示唆しています。 そのムーブにより、それに対してlockを呼び出し、結果を2番目のスレッドのnum2に保持しようとした時に、 counterをキャプチャすることを妨げています!ゆえに、コンパイラは、counterの所有権を複数のスレッドに移すことはできないと教えてくれています。 これは、以前では確認しづらかったことです。なぜなら、スレッドはループの中にあり、 ループの違う繰り返しにある違うスレッドをコンパイラは指し示せないからです。 第15章で議論した複数所有権メソッドによりコンパイルエラーを修正しましょう。 複数のスレッドで複数の所有権 第15章で、スマートポインタのRcを使用して参照カウントの値を作ることで、1つの値に複数の所有者を与えました。 同じことをここでもして、どうなるか見ましょう。リスト16-14でRcにMutexを包含し、 所有権をスレッドに移す前にRcをクローンします。今やエラーを確認したので、 forループの使用に立ち戻り、クロージャにmoveキーワードを使用し続けます。 ファイル名: src/main.rs use std::rc::Rc;\nuse std::sync::Mutex;\nuse std::thread; fn main() { let counter = Rc::new(Mutex::new(0)); let mut handles = vec![]; for _ in 0..10 { let counter = Rc::clone(&counter); let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } println!(\"Result: {}\", *counter.lock().unwrap());\n} リスト16-14: Rcを使用して複数のスレッドにMutexを所有させようとする 再三、コンパイルし……別のエラーが出ました!コンパイラはいろんなことを教えてくれています。 error[E0277]: the trait bound `std::rc::Rc>:\nstd::marker::Send` is not satisfied in `[closure@src/main.rs:11:36:\n15:10 counter:std::rc::Rc>]`\n(エラー: トレイト境界`std::rc::Rc>:\nstd::marker::Send`は`[closure@src/main.rs:11:36:15:10\ncounter:std::rc::Rc>]`で満たされていません) --> src/main.rs:11:22 |\n11 | let handle = thread::spawn(move || { | ^^^^^^^^^^^^^ `std::rc::Rc>`\ncannot be sent between threads safely (`std::rc::Rc>`は、スレッド間で安全に送信できません) | = help: within `[closure@src/main.rs:11:36: 15:10\ncounter:std::rc::Rc>]`, the trait `std::marker::Send` is\nnot implemented for `std::rc::Rc>` (ヘルプ: `[closure@src/main.rs:11:36 15:10 counter:std::rc::Rc>]`内でトレイト`std::marker::Send`は、 `std::rc::Rc>`に対して実装されていません) = note: required because it appears within the type\n`[closure@src/main.rs:11:36: 15:10 counter:std::rc::Rc>]` (注釈: 型`[closure@src/main.rs:11:36 15:10 counter:std::rc::Rc>]`内に出現するので必要です) = note: required by `std::thread::spawn` (注釈: `std::thread::spawn`により必要とされています) おお、このエラーメッセージはとても長ったらしいですね!こちらが、注目すべき重要な部分です: 最初のインラインエラーは `std::rc::Rc>` cannot be sent between threads safelyと述べています。この理由は、エラーメッセージの次に注目すべき重要な部分にあります。 洗練されたエラーメッセージは、 the trait bound `Send` is not satisfiedと述べています。 Sendについては、次の節で語ります: スレッドとともに使用している型が並行な場面で使われることを意図したものであることを保証するトレイトの1つです。 残念ながら、Rcはスレッド間で共有するには安全ではないのです。Rcが参照カウントを管理する際、 cloneが呼び出されるたびにカウントを追加し、クローンがドロップされるたびにカウントを差し引きます。 しかし、並行基本型を使用してカウントの変更が別のスレッドに妨害されないことを確認していないのです。 これは間違ったカウントにつながる可能性があり、今度はメモリリークや、使用し終わる前に値がドロップされることにつながる可能性のある潜在的なバグです。 必要なのは、いかにもRcのようだけれども、参照カウントへの変更をスレッドセーフに行うものです。 Arcで原子的な参照カウント 幸いなことに、ArcはRcのような並行な状況で安全に使用できる型 です 。 a は atomic を表し、原子的に参照カウントする型を意味します。アトミックは、 ここでは詳しく講義しない並行性の別の基本型です: 詳細は、 std::sync::atomicの標準ライブラリドキュメンテーションを参照されたし。現時点では、 アトミックは、基本型のように動くけれども、スレッド間で共有しても安全なことだけ知っていれば良いです。 そうしたらあなたは、なぜ全ての基本型がアトミックでなく、標準ライブラリの型も標準でArcを使って実装されていないのか疑問に思う可能性があります。 その理由は、スレッド安全性が、本当に必要な時だけ支払いたいパフォーマンスの犠牲とともに得られるものだからです。 シングルスレッドで値に処理を施すだけなら、アトミックが提供する保証を強制する必要がない方がコードはより速く走るのです。 例に回帰しましょう: ArcとRcのAPIは同じなので、use行とnewの呼び出しとcloneの呼び出しを変更して、 プログラムを修正します。リスト16-15は、ようやくコンパイルでき、動作します: ファイル名: src/main.rs use std::sync::{Mutex, Arc};\nuse std::thread; fn main() { let counter = Arc::new(Mutex::new(0)); let mut handles = vec![]; for _ in 0..10 { let counter = Arc::clone(&counter); let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } println!(\"Result: {}\", *counter.lock().unwrap());\n} リスト16-15: Arcを使用してMutexをラップし、所有権を複数のスレッド間で共有できるようにする このコードは、以下のように出力します: Result: 10 やりました!0から10まで数え上げました。これは、あまり印象的ではないように思えるかもしれませんが、 本当にMutexとスレッド安全性についていろんなことを教えてくれました。このプログラムの構造を使用して、 カウンタをインクリメントする以上の複雑な処理を行うこともできるでしょう。この手法を使えば、 計算を独立した部分に小分けにし、その部分をスレッドに分割し、それからMutexを使用して、 各スレッドに最終結果を更新させることができます。","breadcrumbs":"恐れるな!並行性 » ミューテックスを使用して一度に1つのスレッドからデータにアクセスすることを許可する","id":"299","title":"ミューテックスを使用して一度に1つのスレッドからデータにアクセスすることを許可する"},"3":{"body":"Rustは、様々な理由により多くの人にとって理想的です。いくつか最も重要なグループを見ていきましょう。","breadcrumbs":"Rustは誰のためのものなの","id":"3","title":"Rustは誰のためのものなの"},"30":{"body":"既にRustの旅の素晴らしいスタートを切っています!この章では、以下の方法を学びました: rustupで最新の安定版のRustをインストールする方法 新しいRustのバージョンに更新する方法 ローカルにインストールされたドキュメンテーションを開く方法 直接rustcを使用してHello, world!プログラムを書き、実行する方法 Cargoの慣習を使用して新しいプロジェクトを作成し、実行する方法 より中身のあるプログラムをビルドし、Rustコードの読み書きに慣れるいいタイミングです。故に、第2章では、 数当てゲームを構築します。むしろ一般的なプログラミングの概念がRustでどう動くのか学ぶことから始めたいのであれば、 第3章を見て、それから第2章に戻ってください。","breadcrumbs":"事始め » まとめ","id":"30","title":"まとめ"},"300":{"body":"counterは不変なのに、その内部にある値への可変参照を得ることができたことに気付いたでしょうか; つまり、Mutexは、Cell系のように内部可変性を提供するわけです。 第15章でRefCellを使用してRcの内容を可変化できるようにしたのと同様に、 Mutexを使用してArcの内容を可変化しているのです。 気付いておくべき別の詳細は、Mutexを使用する際にあらゆる種類のロジックエラーからは、 コンパイラは保護してくれないということです。第15章でRcは、循環参照を生成してしまうリスクを伴い、 そうすると、2つのRcの値がお互いを参照し合い、メモリリークを引き起こしてしまうことを思い出してください。 同様に、Mutexは デッドロック を生成するリスクを伴っています。これは、処理が2つのリソースをロックする必要があり、 2つのスレッドがそれぞれにロックを1つ獲得して永久にお互いを待ちあってしまうときに起こります。 デッドロックに興味があるのなら、デッドロックのあるRustプログラムを組んでみてください; それからどんな言語でもいいので、ミューテックスに対してデッドロックを緩和する方法を調べて、 Rustで是非、それを実装してみてください。MutexとMutexGuardに関する標準ライブラリのAPIドキュメンテーションは、 役に立つ情報を提供してくれます。 SendとSyncトレイトと、それらを独自の型で使用する方法について語って、この章を締めくくります。","breadcrumbs":"恐れるな!並行性 » RefCell/RcとMutex/Arcの類似性","id":"300","title":"RefCell/RcとMutex/Arcの類似性"},"301":{"body":"面白いことに、Rust言語には、 寡 少な並行性機能があります。この章でここまでに語った並行性機能のほとんどは、 標準ライブラリの一部であり、言語ではありません。並行性を扱う選択肢は、言語や標準ライブラリに制限されません; 独自の並行性機能を書いたり、他人が書いたものを利用したりできるのです。 ですが、2つの並行性概念が言語に埋め込まれています: std::markerトレイトのSyncとSendです。","breadcrumbs":"恐れるな!並行性 » SyncとSendトレイトで拡張可能な並行性","id":"301","title":"SyncとSendトレイトで拡張可能な並行性"},"302":{"body":"Sendマーカートレイトは、Sendを実装した型の所有権をスレッド間で転送できることを示唆します。 Rustのほとんどの型はSendですが、Rcを含めて一部例外があります: この型は、Rcの値をクローンし、 クローンしたものの所有権を別のスレッドに転送しようとしたら、両方のスレッドが同時に参照カウントを更新できてしまうので、 Sendになり得ません。このため、Rcはスレッド安全性のためのパフォーマンスの犠牲を支払わなくても済む、 シングルスレッド環境で使用するために実装されているわけです。 故に、Rustの型システムとトレイト境界により、Rcの値を不安全にスレッド間で誤って送信することが絶対ないよう保証してくれるのです。 リスト16-14でこれを試みた時には、the trait Send is not implemented for Rc>というエラーが出ました。 SendのArcに切り替えたら、コードはコンパイルできたわけです。 完全にSendの型からなる型も全て自動的にSendと印付けされます。生ポインタを除くほとんどの基本型もSendで、 生ポインタについては第19章で議論します。","breadcrumbs":"恐れるな!並行性 » Sendでスレッド間の所有権の転送を許可する","id":"302","title":"Sendでスレッド間の所有権の転送を許可する"},"303":{"body":"Syncマーカートレイトは、Syncを実装した型は、複数のスレッドから参照されても安全であることを示唆します。 言い換えると、&T(Tへの参照)がSendなら、型TはSyncであり、参照が他のスレッドに安全に送信できることを意味します。 Send同様、基本型はSyncであり、Syncの型からのみ構成される型もまたSyncです。 Sendではなかったのと同じ理由で、スマートポインタのRcもまたSyncではありません。 RefCell型(これについては第15章で話しました)と関連するCell系についてもSyncではありません。 RefCellが実行時に行う借用チェックの実装は、スレッド安全ではないのです。 スマートポインタのMutexはSyncで、「複数のスレッド間でMutexを共有する」節で見たように、 複数のスレッドでアクセスを共有するのに使用することができます。","breadcrumbs":"恐れるな!並行性 » Syncで複数のスレッドからのアクセスを許可する","id":"303","title":"Syncで複数のスレッドからのアクセスを許可する"},"304":{"body":"SendとSyncトレイトから構成される型は自動的にSendとSyncにもなるので、 それらのトレイトを手動で実装する必要はありません。マーカートレイトとして、 実装すべきメソッドさえも何もありません。並行性に関連する不変条件を強制することに役立つだけなのです。 これらのトレイトを手動で実装するには、unsafeなRustコードを実装することが関わってきます。 unsafeなRustコードを使用することについては第19章で語ります; とりあえず、重要な情報は、 SendとSyncではない部品からなる新しい並行な型を構成するには、安全性保証を保持するために、 注意深い思考が必要になるということです。 The Rustonomicon には、 これらの保証とそれを保持する方法についての情報がより多くあります。 訳注: 日本語版のThe Rustonomiconは こちら です。","breadcrumbs":"恐れるな!並行性 » SendとSyncを手動で実装するのは非安全である","id":"304","title":"SendとSyncを手動で実装するのは非安全である"},"305":{"body":"この本において並行性を見かけるのは、これで最後ではありません: 第20章のプロジェクトでは、 この章の概念をここで議論した微小な例よりもより現実的な場面で使用するでしょう。 前述のように、Rustによる並行性の取扱いのごく一部のみが言語仕様なので、多くの並行性の解決策は クレートとして実装されています。これらは標準ライブラリよりも迅速に進化するので、 マルチスレッド環境で使用すべき現在の最先端のクレートを必ずネットで検索してください。 Rustの標準ライブラリは、メッセージ受け渡しにチャンネルを、並行の文脈で安全に使用できる、 MutexやArcなどのスマートポインタ型を提供しています。型システムと借用チェッカーにより、 これらの解決策を使用するコードがデータ競合や無効な参照に行き着かないことを保証してくれます。 一旦コードをコンパイルすることができたら、他の言語ではありふれている追跡困難な類のバグなしに、 複数のスレッドでも喜んで動くので安心できます。並行プログラミングは、もはや恐れるべき概念ではありません: 恐れることなく前進し、プログラムを並行にしてください! 次は、Rustプログラムが肥大化するにつれて問題をモデル化し、解決策を構造化する慣例的な方法について話します。 さらに、Rustのイディオムがオブジェクト指向プログラミングで馴染み深いかもしれないイディオムにどのように関連しているかについても議論します。","breadcrumbs":"恐れるな!並行性 » まとめ","id":"305","title":"まとめ"},"306":{"body":"オブジェクト指向プログラミング(OOP)は、プログラムをモデル化する手段です。オブジェクトは、 1960年代のSimulaに端緒を発しています。このオブジェクトは、 お互いにメッセージを渡し合うというアラン・ケイ(Alan Kay)のプログラミングアーキテクチャに影響を及ぼしました。 彼は、このアーキテクチャを解説するために、 オブジェクト指向プログラミング という用語を造語しました。 多くの競合する定義がOOPが何かを解説しています; Rustをオブジェクト指向と区分する定義もありますし、 しない定義もあります。この章では、広くオブジェクト指向と捉えられる特定の特徴と、 それらの特徴がこなれたRustでどう表現されるかを探究します。それからオブジェクト指向のデザインパターンをRustで実装する方法を示し、 そうすることとRustの強みを活用して代わりの解決策を実装する方法の代償を議論します。","breadcrumbs":"Rustのオブジェクト指向プログラミング機能","id":"306","title":"Rustのオブジェクト指向プログラミング機能"},"307":{"body":"言語がオブジェクト指向と考えられるのになければならない機能について、プログラミングコミュニティ内での総意はありません。 RustはOOPを含めた多くのプログラミングパラダイムに影響を受けています; 例えば、 第13章で関数型プログラミングに由来する機能を探究しました。議論はあるかもしれませんが、 OOP言語は特定の一般的な特徴を共有しています。具体的には、オブジェクトやカプセル化、 継承などです。それらの個々の特徴が意味するものとRustがサポートしているかを見ましょう。","breadcrumbs":"Rustのオブジェクト指向プログラミング機能 » オブジェクト指向言語の特徴","id":"307","title":"オブジェクト指向言語の特徴"},"308":{"body":"エーリヒ・ガンマ(Enoch Gamma)、リチャード・ヘルム(Richard Helm)、ラルフ・ジョンソン(Ralph Johnson)、 ジョン・ブリシディース(John Vlissides)(アディソン・ワズリー・プロ)により、 1994年に書かれた デザインパターン: 再利用可能なオブジェクト指向ソフトウェアの要素 という本は、 俗に 4人のギャングの本 (訳注: the Gang of Four book; GoFとよく略される)と呼ばれ、オブジェクト指向デザインパターンのカタログです。 そこでは、OOPは以下のように定義されています: オブジェクト指向プログラムは、オブジェクトで構成される。オブジェクトは、 データとそのデータを処理するプロシージャを梱包している。このプロシージャは、 典型的に メソッド または オペレーション と呼ばれる。 この定義を使用すれば、Rustはオブジェクト指向です: 構造体とenumにはデータがありますし、 implブロックが構造体とenumにメソッドを提供します。メソッドのある構造体とenumは、 オブジェクトとは呼ばれないものの、GoFのオブジェクト定義によると、同じ機能を提供します。","breadcrumbs":"Rustのオブジェクト指向プログラミング機能 » オブジェクトは、データと振る舞いを含む","id":"308","title":"オブジェクトは、データと振る舞いを含む"},"309":{"body":"OOPとよく紐づけられる別の側面は、カプセル化の思想です。これは、オブジェクトの実装詳細は、 そのオブジェクトを使用するコードにはアクセスできないことを意味します。故に、 オブジェクトと相互作用する唯一の手段は、その公開APIを通してです; オブジェクトを使用するコードは、 オブジェクトの内部に到達して、データや振る舞いを直接変更できるべきではありません。 このために、プログラマはオブジェクトの内部をオブジェクトを使用するコードを変更する必要なく、 変更しリファクタリングできます。 カプセル化を制御する方法は、第7章で議論しました: pubキーワードを使用して、 自分のコードのどのモジュールや型、関数、メソッドを公開するか決められ、 既定ではそれ以外のものは全て非公開になります。例えば、 i32値のベクタを含むフィールドのあるAveragedCollectionという構造体を定義できます。 この構造体はさらに、ベクタの値の平均を含むフィールドを持てます。つまり、平均は誰かが必要とする度に、 オンデマンドで計算する必要はないということです。言い換えれば、AveragedCollectionは、 計算した平均をキャッシュしてくれるわけです。リスト17-1には、AveragedCollection構造体の定義があります: ファイル名: src/lib.rs pub struct AveragedCollection { list: Vec, average: f64,\n} リスト17-1: 整数のリストとコレクションの要素の平均を管理するAveragedCollection構造体 構造体は、他のコードが使用できるようにpubで印づけされていますが、構造体のフィールドは非公開のままです。 値が追加されたりリストから削除される度に、平均も更新されることを保証したいので、今回の場合重要です。 addやremove、averageメソッドを構造体に実装することでこれをします。リスト17-2のようにですね: ファイル名: src/lib.rs # pub struct AveragedCollection {\n# list: Vec,\n# average: f64,\n# }\nimpl AveragedCollection { pub fn add(&mut self, value: i32) { self.list.push(value); self.update_average(); } pub fn remove(&mut self) -> Option { let result = self.list.pop(); match result { Some(value) => { self.update_average(); Some(value) }, None => None, } } pub fn average(&self) -> f64 { self.average } fn update_average(&mut self) { let total: i32 = self.list.iter().sum(); self.average = total as f64 / self.list.len() as f64; }\n} リスト17-2: AveragedCollectionのadd、remove、average公開メソッドの実装 add、remove、averageの公開メソッドがAveragedCollectionのインスタンスを変更する唯一の方法になります。 要素がaddメソッドを使用してlistに追加されたり、removeメソッドを使用して削除されたりすると、 各メソッドの実装がaverageフィールドの更新を扱う非公開のupdate_averageメソッドも呼び出します。 listとaverageフィールドを非公開のままにしているので、外部コードが要素をlistフィールドに直接追加したり削除したりする方法はありません; そうでなければ、averageフィールドは、listが変更された時に同期されなくなる可能性があります。 averageメソッドはaverageフィールドの値を返し、外部コードにaverageを読ませるものの、 変更は許可しません。 構造体AveragedCollectionの実装詳細をカプセル化したので、データ構造などの側面を将来容易に変更することができます。 例を挙げれば、listフィールドにVecではなくHashSetを使うこともできます。 add、remove、averageといった公開メソッドのシグニチャが同じである限り、AveragedCollectionを使用するコードは変更する必要がないでしょう。 代わりにlistを公開にしたら、必ずしもこうはならないでしょう: HashSetとVecは、 要素の追加と削除に異なるメソッドを持っているので、外部コードが直接listを変更しているなら、 外部コードも変更しなければならない可能性が高いでしょう。 カプセル化が、言語がオブジェクト指向と考えられるのに必要な側面ならば、Rustはその条件を満たしています。 コードの異なる部分でpubを使用するかしないかという選択肢のおかげで、実装詳細をカプセル化することが可能になります。","breadcrumbs":"Rustのオブジェクト指向プログラミング機能 » カプセル化は、実装詳細を隠蔽する","id":"309","title":"カプセル化は、実装詳細を隠蔽する"},"31":{"body":"実物のプロジェクトに一緒に取り組むことで、Rustの世界へ飛び込みましょう! この章では、実際のプログラム内で使用しながらいくつかの一般的なRustの概念に触れます。 let、match、メソッド、関連関数、外部クレートの使用などについて学ぶでしょう! 後ほどの章でこれらの概念について深く知ることになります。この章では、基礎部分だけにしましょう。 古典的な初心者向けのプログラミング問題を実装してみましょう: 数当てゲームです。 これは以下のように動作します: プログラムは1から100までの乱数整数を生成します。 そしてプレーヤーに予想を入力するよう促します。予想を入力したら、プログラムは、 その予想が小さすぎたか大きすぎたかを出力します。予想が当たっていれば、ゲームは祝福メッセージを表示し、 終了します。","breadcrumbs":"数当てゲームをプログラムする","id":"31","title":"数当てゲームをプログラムする"},"310":{"body":"継承 は、それによってオブジェクトが他のオブジェクトの定義から受け継ぐことができる機構であり、 それ故に、再定義する必要なく、親オブジェクトのデータと振る舞いを得ます。 言語がオブジェクト指向言語であるために継承がなければならないのならば、Rustは違います。 親構造体のフィールドとメソッドの実装を受け継ぐ構造体を定義する方法はありません。しかしながら、 継承がプログラミング道具箱にあることに慣れていれば、そもそも継承に手を伸ばす理由によって、 Rustで他の解決策を使用することができます。 継承を選択する理由は主に2つあります。1つ目は、コードの再利用です: ある型に特定の振る舞いを実装し、 継承により、その実装を他の型にも再利用できるわけです。デフォルトのトレイトメソッド実装を代わりに使用して、 Rustコードを共有でき、これは、リスト10-14でSummaryトレイトにsummarizeメソッドのデフォルト実装を追加した時に見かけました。 Summaryトレイトを実装する型は全て、追加のコードなくsummarizeメソッドが使用できます。 これは、親クラスにメソッドの実装があり、継承した子クラスにもそのメソッドの実装があることと似ています。 また、Summaryトレイトを実装する時に、summarizeメソッドのデフォルト実装を上書きすることもでき、 これは、親クラスから継承したメソッドの実装を子クラスが上書きすることに似ています。 継承を使用するもう1つの理由は、型システムに関連しています: 親の型と同じ箇所で子供の型を使用できるようにです。 これは、 多相性 (polymorphism)とも呼ばれ、複数のオブジェクトが特定の特徴を共有しているなら、 実行時にお互いに代用できることを意味します。","breadcrumbs":"Rustのオブジェクト指向プログラミング機能 » 型システム、およびコード共有としての継承","id":"310","title":"型システム、およびコード共有としての継承"},"311":{"body":"多くの人にとって、多相性は、継承の同義語です。ですが、実際には複数の型のデータを取り扱えるコードを指すより一般的な概念です。 継承について言えば、それらの型は一般的にはサブクラスです。 Rustは代わりにジェネリクスを使用して様々な可能性のある型を抽象化し、トレイト境界を使用してそれらの型が提供するものに制約を課します。 これは時に、 パラメータ境界多相性 (bounded parametric polymorphism)と呼ばれます。 継承は、近年、多くのプログラミング言語において、プログラムの設計解決策としては軽んじられています。 というのも、しばしば必要以上にコードを共有してしまう危険性があるからです。サブクラスは、 必ずしも親クラスの特徴を全て共有するべきではないのに、継承ではそうなってしまうのです。 これにより、プログラムの設計の柔軟性を失わせることもあります。また、道理に合わなかったり、メソッドがサブクラスには適用されないために、 エラーを発生させるようなサブクラスのメソッドの呼び出しを引き起こす可能性が出てくるのです。 さらに、サブクラスに1つのクラスからだけ継承させる言語もあり、さらにプログラムの設計の柔軟性が制限されます。 これらの理由により、継承ではなくトレイトオブジェクトを使用してRustは異なるアプローチを取っています。 Rustにおいて、トレイトオブジェクトがどう多相性を可能にするかを見ましょう。","breadcrumbs":"Rustのオブジェクト指向プログラミング機能 » 多相性","id":"311","title":"多相性"},"312":{"body":"第8章で、ベクタの1つの制限は、たった1つの型の要素を保持することしかできないことだと述べました。 リスト8-10で整数、浮動小数点数、テキストを保持する列挙子のあるSpreadsheetCell enumを定義して、 これを回避しました。つまり、各セルに異なる型のデータを格納しつつ、1行のセルを表すベクタを保持するということです。 コンパイル時にわかるある固定されたセットの型にしか取り替え可能な要素がならない場合には、 完璧な解決策です。 ところが、時として、ライブラリの使用者が特定の場面で合法になる型のセットを拡張できるようにしたくなることがあります。 これをどう実現する可能性があるか示すために、各アイテムにdrawメソッドを呼び出してスクリーンに描画するという、 GUIツールで一般的なテクニックをしてあるリストの要素を走査する例のGUIツールを作ります。 GUIライブラリの構造を含むguiと呼ばれるライブラリクレートを作成します。 このクレートには、他人が使用できるButtonやTextFieldなどの型が包含されるかもしれません。 さらに、guiの使用者は、描画可能な独自の型を作成したくなるでしょう: 例えば、 ある人はImageを追加し、別の人はSelectBoxを追加するかもしれません。 この例のために本格的なGUIライブラリは実装するつもりはありませんが、部品がどう組み合わさるかは示します。 ライブラリの記述時点では、他のプログラマが作成したくなる可能性のある型全てを知る由もなければ、定義することもできません。 しかし、guiは異なる型の多くの値を追いかけ、この異なる型の値に対してdrawメソッドを呼び出す必要があることは、 確かにわかっています。drawメソッドを呼び出した時に正確に何が起きるかを知っている必要はありません。 値にそのメソッドが呼び出せるようあることだけわかっていればいいのです。 継承のある言語でこれを行うには、drawという名前のメソッドがあるComponentというクラスを定義するかもしれません。 Button、Image、SelectBoxなどの他のクラスは、Componentを継承し、故にdrawメソッドを継承します。 個々にdrawメソッドをオーバーライドして、独自の振る舞いを定義するものの、フレームワークは、 Componentインスタンスであるかのようにその型全部を扱い、この型に対してdrawを呼び出します。 ですが、Rustに継承は存在しないので、使用者に新しい型で拡張してもらうためにguiライブラリを構成する他の方法が必要です。","breadcrumbs":"Rustのオブジェクト指向プログラミング機能 » トレイトオブジェクトで異なる型の値を許容する","id":"312","title":"トレイトオブジェクトで異なる型の値を許容する"},"313":{"body":"guiに欲しい振る舞いを実装するには、drawという1つのメソッドを持つDrawというトレイトを定義します。 それから トレイトオブジェクト を取るベクタを定義できます。トレイトオブジェクトは、 指定したトレイトを実装するある型のインスタンスを指します。&参照やBoxスマートポインタなどの、 何らかのポインタを指定し、それから関係のあるトレイトを指定する(トレイトオブジェクトがポインタを使用しなければならない理由については、 第19章の「動的サイズ付け型とSizedトレイト」節で語ります)ことでトレイトオブジェクトを作成します。 ジェネリックまたは具体的な型があるところにトレイトオブジェクトは使用できます。どこでトレイトオブジェクトを使用しようと、 Rustの型システムは、コンパイル時にその文脈で使用されているあらゆる値がそのトレイトオブジェクトのトレイトを実装していることを保証します。 結果としてコンパイル時に可能性のある型を全て知る必要はなくなるのです。 Rustでは、構造体とenumを他の言語のオブジェクトと区別するために「オブジェクト」と呼ぶことを避けていることに触れましたね。 構造体やenumにおいて、構造体のフィールドのデータやimplブロックの振る舞いは区分けされているものの、 他の言語では1つの概念に押し込められるデータと振る舞いは、しばしばオブジェクトと分類されます。 しかしながら、トレイトオブジェクトは、データと振る舞いをごちゃ混ぜにするという観点で他の言語のオブジェクトに近い です 。 しかし、トレイトオブジェクトは、データを追加できないという点で伝統的なオブジェクトと異なっています。 トレイトオブジェクトは、他の言語のオブジェクトほど一般的に有用ではありません: その特定の目的は、共通の振る舞いに対して抽象化を行うことです。 リスト17-3は、drawという1つのメソッドを持つDrawというトレイトを定義する方法を示しています: ファイル名: src/lib.rs pub trait Draw { fn draw(&self);\n} リスト17-3: Drawトレイトの定義 この記法は、第10章のトレイトの定義方法に関する議論で馴染み深いはずです。その次は、新しい記法です: リスト17-4では、componentsというベクタを保持するScreenという名前の構造体を定義しています。 このベクタの型はBoxで、これはトレイトオブジェクトです; Drawトレイトを実装するBox内部の任意の型に対する代役です。 ファイル名: src/lib.rs # pub trait Draw {\n# fn draw(&self);\n# }\n#\npub struct Screen { pub components: Vec>,\n} リスト17-4: Drawトレイトを実装するトレイトオブジェクトのベクタを保持するcomponentsフィールドがある Screen構造体の定義 Screen構造体に、componentsの各要素に対してdrawメソッドを呼び出すrunというメソッドを定義します。 リスト17-5のようにですね: ファイル名: src/lib.rs # pub trait Draw {\n# fn draw(&self);\n# }\n#\n# pub struct Screen {\n# pub components: Vec>,\n# }\n#\nimpl Screen { pub fn run(&self) { for component in self.components.iter() { component.draw(); } }\n} リスト17-5: 各コンポーネントに対してdrawメソッドを呼び出すScreenのrunメソッド これは、トレイト境界を伴うジェネリックな型引数を使用する構造体を定義するのとは異なる動作をします。 ジェネリックな型引数は、一度に1つの具体型にしか置き換えられないのに対して、トレイトオブジェクトは、 実行時にトレイトオブジェクトに対して複数の具体型で埋めることができます。例として、 ジェネリックな型とトレイト境界を使用してリスト17-6のようにScreen構造体を定義することもできました: ファイル名: src/lib.rs # pub trait Draw {\n# fn draw(&self);\n# }\n#\npub struct Screen { pub components: Vec,\n} impl Screen where T: Draw { pub fn run(&self) { for component in self.components.iter() { component.draw(); } }\n} リスト17-6: ジェネリクスとトレイト境界を使用したScreen構造体とrunメソッドの対立的な実装 こうすると、全てのコンポーネントの型がButtonだったり、TextFieldだったりするScreenのインスタンスに制限されてしまいます。 絶対に同種のコレクションしか持つ予定がないのなら、ジェネリクスとトレイト境界は、 定義がコンパイル時に具体的な型を使用するように単相化されるので、望ましいです。 一方で、メソッドがトレイトオブジェクトを使用すると、1つのScreenインスタンスが、 Box