The book 第9章のつづきから

ここから. Recoverable Errors with Result - The Rust Programming Language

ちょいちょい見てきたunwrapResult型のメソッドで,Okなら中身を取り出す一方,エラーに遭遇した場合はパニクる.

Returns the contained Ok value, consuming the self value.

Because this function may panic, its use is generally discouraged. Instead, prefer to use pattern matching and handle the Err case explicitly, or call unwrap_or, unwrap_or_else, or unwrap_or_default.

ただしunwrapは引数を取らず,エラーメッセージは固定.expect(数当てゲームで見た)はエラーメッセージをカスタマイズできる.

Recoverable Errors with Result - The Rust Programming Language

↑ヘッダーにもリンク付けられるんだ.

エラーの伝播について.関数内部で発生したエラーの処理の判断をその呼び出しもとに委ねたいときはその関数自身もResult型を返すようにすればよい.

Throwしない!投げない!throwはキーワード(予約語)にすら含まれていない.

その証拠にthrowという変数名は自由に使える.catchもいいがtryはダメ.今のところ役割はないが,2018エディションからキーワード入りしている.

    let throw = "投";
    let catch = "掴";
    //let try = "試";

Recoverable Errors with Result - The Rust Programming Language

match式のアーム中でErrreturnする処理は,Result?を後置することで代用できる.

ただしmatchで書いた場合と異なる点があり,?はその関数から発生するエラーをまとめ上げたエラー型に変換してくれる(という理解で正しいだろうか).

?はさらにメソッドを繋げることもできる.

fn read_user_name_from_file(file_name: &str) -> Result<String, io::Error> {
    let mut s = String::new();
    File::open(file_name)?.read_to_string(&mut s)?;
    Ok(s)
}

おおー.?.はTSでも見るが意味が違う.

と思ったら必ずしもそうとは言い切れない.Optionの中でNoneを早期リターンすることもできて,その場合nullチェックを行うTSの用法に似ている.

When applied to values of the Option type, it propagates Nones.

Operator expressions - The Rust Reference

ただしあくまでもnullを返すわけではない.OptionResult内にしか登場できない.

実はファイルを読む操作はもっと簡単に書けて,std::fs::read_to_stringだけで済む.

これはいい記事かも.

Rust のエラーハンドリングはシンタックスシュガーが豊富で完全に初見殺しなので自信を持って使えるように整理してみたら完全に理解した - Qiita

Recoverable Errors with Result - The Rust Programming Language

?std::ops::Tryトレイトを実装した型を戻り値に取る関数内部でしか使えない.つまり標準ではOptionResult

ただしmainは条件付きで例外で,Result<(), Box<dyn Error>>を返すように書けば使える.

dynとは…….

dyn - Rust

To panic! or Not To panic! - The Rust Programming Language

関数を書くときは,panic!するかしないかの判断はその呼び出しもとに丸投げしてよいから,Resultで書くのがデフォルトの選択になる.ではいつパニックするコードを書くべきか?

コード例を書くとき,プロトタイピングをするときにロバストなエラー処理をする必要はない.テストもある場所でパニックしたら回復してはいけない.panic!はどこで問題が起こったか教えてくれるから.

コンパイラによって処理できないロジックで必ずOkが返ることが分かる箇所では,原理的にはpanic!を返しうるコードを書いてもよい. 明らかに有効なIPアドレスのパースなど.ベクターのサイズを判定したうえでpop().unwrap()するのもこの例か?

Bad stateに陥った場合にpanic!するのもよい.これは脆弱性につながるため開発時に取り除くべきバグで, 標準ライブラリーのメモリ割り当て領域外参照がパニクるのはこの理由による.

値が不正かどうか検査するのは骨の折れる作業だが,型チェックはこれを軽減してくれている.Nullチェックを手で入れる必要はない.

To panic! or Not To panic! - The Rust Programming Language

数当てゲームのバリデーションのブラッシュアップ.入力値が整数としてパース可能でも1から100までの間でなければ弾きたい. しかし値のチェックを毎回要求すると,関数としての再利用可能性を損ねる.

この場合,1から100までの値のみをフィールドに持つ構造体を定義して,単に値で持つのではなくこの構造体を返して値のやり取りを行うことにするとよい.newメソッドではこの範囲外の値で初期化しようとするとパニクる.フィールドはパブリックにはせず,ゲッターメソッドを用意する.

結局パニクらないためにはnewするときにチェックが要るのか,と思ったらこの例は”contract”を破るから,ということらしい. 必ずnewを通して初期化するため,そこ以外でのチェックは要らない. パニクるのはどんなときかAPI documentationに明記するから,値のチェックが分散するということもない.

第10章 Generic Types, Traits, and Lifetimes - The Rust Programming Language

ジェネリクスとトレイトとライフタイム.ライフタイム?  ライフタイムはジェネリクスの一種で,参照の有効性を担保しつつ借用を可能にする. 構造体をフィールドに持つ構造体がうまく扱えなかったのはこれを理解していないためだろうか.

最大値を返す関数.せっかくなのでOptionを使う.

fn main() {
    let list = [3, 2, 4, 5, 1];
    println!("The largest numer is {}", largest(&list).unwrap());
}

fn largest(list: &[i32]) -> Option<i32> {
    if list.len() == 0 {
        return None;
    }
    let mut largest = list[0];
    for &item in list {
        if item > largest {
            largest = item;
        }
    }
    Some(largest)
}

10.1 Generic Data Types - The Rust Programming Language

Rustでも型パラメーターには慣習としてTから使う,というのが明言されている.

- fn largest(list: &[i32]) -> Option<i32> {
+ fn largest<T>(list: &[T]) -> Option<T> {

こうしてジェネラライズ?しようとするとコンパイラに怒られる.

error[E0369]: binary operation `>` cannot be applied to type `T`
  --> src\main.rs:12:17
   |
12 |         if item > largest {
   |            ---- ^ ------- T
   |            |
   |            T
   |
help: consider restricting type parameter `T`
   |
6  | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> Option<T> {
   |             ^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to previous error

それにしても分かりやすいな.一旦the bookのほう読まずにやってみよう.

implementsキーワードではなくてコロンを使うと知った.アドバイスに従うと,

+ use std::cmp::PartialOrd;

- fn largest(list: &[i32]) -> Option<i32> {
+ fn largest<T: PartialOrd>(list: &[T]) -> Option<T> {

まだ怒られる.

error[E0508]: cannot move out of type `[T]`, a non-copy slice
  --> src\main.rs:12:23
   |
12 |     let mut largest = list[0];
   |                       ^^^^^^^
   |                       |
   |                       cannot move out of here
   |                       move occurs because `list[_]` has type `T`, which does not implement the `Copy` trait
   |                       help: consider borrowing here: `&list[0]`

error[E0507]: cannot move out of a shared reference
  --> src\main.rs:13:18
   |
13 |     for &item in list {
   |         -----    ^^^^
   |         ||
   |         |data moved here
   |         |move occurs because `item` has type `T`, which does not implement the `Copy` trait
   |         help: consider removing the `&`: `item`

Copyトレイトを実装していないのがいけない.”consider borrowing here: &list[0]“とあるのでそうする.

するとlargestTから参照の&Tに変わるのでそれに合わせて他も変える.

-    let mut largest = list[0];
-    for &item in list {
+    let mut largest = &list[0];
+    for item in list {

-    Some(largest)
+    Some(*largest)

借用している値の参照であるため,デリファレンスによって所有権を関数外にムーブさせることはできない(という使い方であっているだろうか).

error[E0507]: cannot move out of `*largest` which is behind a shared reference
  --> src\main.rs:18:10
   |
18 |     Some(*largest)
   |          ^^^^^^^^ move occurs because `*largest` has type `T`, which does not implement the `Copy` trait

となるとCopyをimplementしてコンパイルするか見よう.

- fn largest<T: PartialOrd>(list: &[T]) -> Option<T> {
+ fn largest<T: PartialOrd, Copy>(list: &[T]) -> Option<T> {

当て推量だが複数のトレイトのimplementはカンマ区切りであっていた.

 --> src\main.rs:5:41
  |
5 |     println!("The largest numer is {}", largest(&list).unwrap());
  |                                         ^^^^^^^ cannot infer type for type parameter `Copy` declared on the function `largest`

型注釈がいる.が,ここで立ち往生してしまう.

-    println!("The largest numer is {}", largest(&list).unwrap());
+    let largest: i32 = largest(&list).unwrap();
+    println!("The largest numer is {}", largest);

こういうことではないらしい.

For more information about this error, try rustc --explain E0282.

とあるので見てみる.

This error indicates that type inference did not result in one unique possible type, and extra information is required. In most cases this can be provided by adding a type annotation. Sometimes you need to specify a generic type parameter manually.

うーむ.largest<i32>としてもダメだ.

で,よく見ると元のエラーメッセージでは

cannot infer type for type parameter Copy declared on the function largest

Copyが型パラメータと見なされていることに気付く.

当て推量だが複数のトレイトのimplementはカンマ区切りであっていた.

あってへんやんけ.というかResultで既に見ていたな.

&で繋ぐか|か…などといくつか試して+に行き当たる.

- fn largest<T: PartialOrd, Copy>(list: &[T]) -> Option<T> {
+ fn largest<T: PartialOrd + Copy>(list: &[T]) -> Option<T> {

するとコンパイルした.

The largest number is 5

(タイポ)

Copyのimplementを要求するというのは実質プリミティブ型を仮定することになっているのだろうか.

Copy in std::marker - Rust

そんなことはなかった.

答え合わせしようと思ったら一旦お預けになっていた!

In Struct Definitions

構造体はstruct Point<T>みたいに型パラメータを入れる.

In Enum Definitions

列挙型.

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

Variantsのパラメータには型が入る.これはジェネリクスでなくても同じだが,意識すると奇妙な感じがする.

In Method Definitions

メソッドの場合はimplの直後にも型パラメータを書く:

impl <T> MyStruct<T>{
    fn myMethod(&self) -> &T{
        ...
    } 
}

implのほうはジェネリクスにしないで,特定の型パラメータを持つ場合にのみ定義されるメソッドを定義することもできる.

impl MyStruct<f32>{

implの型パラメータと構造体のそれが異なっていてもよい.

impl<T> MyStruct<U>{

Performance of Code Using Generics

Monomorphization.ジェネリック型はコンパイル時に具体的な型へ読み替えられるため,実行時にパフォーマンス面でジェネリクスを使わない場合との差異は生じない.

この次traitの話.