2020年12月22日から2020年12月25日 のRustの勉強記録
TRPL pp. 303-324
13.1.3
クロージャやクロージャの呼び出し結果の値を保持する構造体を作る
- 結果の値が必要な場合
- その構造体に格納されている値があるかを確認
- あったら、それを使う。(クロージャを実行しない)
- なければ、クロージャを実行し、その結果の値をキャッシュする
- その構造体に格納されている値があるかを確認
これはメモ化、遅延評価と呼ばれるパターンで、残りのコードは結果を保存し再利用する責任を追わずに済む。
クロージャを保持する構造体を作成したい
- クロージャの型を指定する必要がある。
- 各クロージャインスタンスには独自の匿名の型がある。
- クロージャを使用する構造体、enum、関数引数を定義するには、ジェネリックスとトレイト境界を使用する。
Fn
トレイト境界に型を追加して、このトレイト境界に合致するクロージャが持つべき引数と戻り値の型を示す
- クロージャは以下のいずれかのトレイトを実装する。(なお、関数は3つのトレイト全部を実装する。環境から値をキャプチャする必要がなければ関数を使用できる)
Fn
FnMut
FnOnce
- クロージャとその結果を保存する構造体をimplする
- フィールドは非公開にする
- なぜなら、呼び出し元は変更できないように、つまり、Cacherに構造体のフィールドの値を管理してほしいから。
- 評価結果が欲しければ、
value
メソッドを呼ぶようにするSome
ならば、その結果をNone
ならば、クロージャを呼び出し、その結果を構造体に保存し、その結果を返す
- フィールドは非公開にする
- この構造体のメリット:
- 重い計算を最大1回で済ませられるようにする
- この保証をするのに必要なロジックの面倒は構造体が見る
- 呼び出し元はビジネスロジックに集中できる
クロージャを変数に直接保存する代わりに、クロージャを保持する構造体の新規インスタンスを保存する。
13.1.4
Cacher実装で課題になること: 他の文脈での再利用性
- 常に
value
メソッドの引数arg
に対して、同じ値を想定しており、更新できない。- -> ハッシュマップを保持するようにしよう
- 引数の型、返り値の型が固定で、柔軟ではない
- -> ジェネリックな引数を導入するようにしよう
13.1.5
クロージャは:
- 環境をキャプチャし、
- 自分が定義されたスコープにアクセスできる
- メモリを使用して、クロージャ本体で使用できるように、その値を保存する
- これはオーバーヘッド
- 関数は環境をキャプチャすることが赦されていないので、オーバーヘッドを招かない
- メモリを使用して、クロージャ本体で使用できるように、その値を保存する
クロージャの環境から値をキャプチャする3つの方法
Fn
- 不変借用
FnMut
- 可変借用
FnOnce
- 所有権を奪い、自分にムーブする
- 2回以上奪えないから
Once
- 2回以上奪えないから
- 所有権を奪い、自分にムーブする
所有権を奪うことをクロージャーに強制したいなら、引数リストの前にmove
キーワードを付ける
Fn
トレイトのどれかを指定するほとんどの場合のプラクティス:
Fn
から始める- コンパイラが
FnMut
やFnOnce
が必要な場合は教えてくれる
環境をキャプチャできるクロージャが関数の引数として有用な場面はイテレータ
13.2
イテレータパターンにより、一連の要素に順番に何らかの作業を行うことができる。
イテレータ:
- 各要素を繰り返し、シーケンスが終わったことを決定するロジックの責任を負う。
- イテレータが存在しない言語だと、変数を添え字0から始め、添え字アクセスし、総要素数に到達するまでループするよう、その変数の値をインクリメントする、というロジックを書く必要がある。
- 添え字を使えるデータ構造以外でも同じロジックを使える
イテレータの生成とその使用は別個であるが、for
でまとめてもよい。
13.2.1
Iterator
トレイトは以下のようなものであり、1つのメソッドのみを定義することを実装者に要求する。それはnext
。
pub trait Iterator {
type Item
fn next(&mut self) -> Option(Self::Item)
}
Item
型がイテレータから帰ってくる型になる。これを関連型と言う。
なお、next
は直接呼ぶことも可能。
next
:
- シーケンスのどこにいるかを追いかけるために、イテレータを消費する、各呼び出しごとに、イテレータが使用している内部状態が変わる
- イテレータを可変にする必要がある
for
ループでは、ループがiter
の所有権を奪い、陰で可変にしている。
- イテレータを可変にする必要がある
next
で得られる値は不変な参照
iter
:
into_iter
では、- 元の所有権を奪い、所有された値を返す
- イテレータを生成できる
iter_mut
では、可変参照を繰り返せる
13.2.2
Iterator
トレイトは、デフォルト実装のある多くの異なるメソッドがある。
next
を呼び出すメソッドは、イテレータを消費することから、消費アダプタ(consuming adaptor)と呼ばれる
例: sum
メソッド:
- 各要素を一時的な合計に追加し、繰り返しが完了したらその合計を返す
- イテレータの所有権を奪うので、その後に元のイテレータは使用できなくなる。
13.3
イテレータアダプタ
Iterator
トレイトに定義された消費アダプタ以外のメソッド- イテレータを別の種類のイテレータに変えさせてくれる
- 複数回呼び出しを連結して、複雑な動作を読みやすい形で行える
- 消費アダプタメソッドのどれかを呼び出さないと何もしない
- なぜなら、イテレータは怠惰だから
- 消費アダプタが呼ばれ、消費されるまで、何もしない
- それまでは、
map
で指定したクロージャを実行しない
- それまでは、
- 消費アダプタが呼ばれ、消費されるまで、何もしない
- なぜなら、イテレータは怠惰だから
イテレータアダプタの例: map(クロージャ)
- クロージャをとるので、各要素に対して行いたいどんな処理でも指定できる
Iterator
トレイトが提供する繰り返し動作を再利用しつつ、クロージャにより一部の動作をカスタマイズできる好例である。
13.3.1
filter
- イテレータアダプタ
- イテレータの各要素を取り、論理値を返すクロージャを取る
- クロージャが
true
を返すなら、- 結果のイテレータにその値が含まれる
- クロージャが
false
を返すなら、- 結果のイテレータにその値が含まれない
- クロージャが
- クロージャを取るので、環境から変数をキャプチャうることができる。
13.3.2
イテレータの作成。ベクタ、ハッシュマップなど、コレクション型からできる。
Iterator
トレイトを自分で実装することでしたいことをなんでもするイテレータを作成できる。必要な定義の提供はnext
メソッドのみ。
デモ:
- 構造体を作る
- Iteratorトレイトを実装
impl Iterator for SomeStruct {
type Item = 'a;
fn next(&mut self) -> Option<Self::Item> {
...
}
}
これでnext
メソッドも他のIteratorトレイトメソッドを使用することができるようになる。
デモで使っているzip
は、入力イテレータのどちらかがNoneを返したら、Noneを返す。
13.4.1
やりたいこと: 非効率なclone
呼び出しを除外したい
疑問: なぜclone
を使っていたか?
- スライスを借用していたから
- Configインスタンスの所有権を返すため
方法: イテレータでどうするか?
- 引数としてイテレータの所有権を奪うように変更する
- 借用する添え字アクセス処理をやめ、
- イテレータからString値をConfigにムーブするようにする
13.4.1.1
env::args
:
- イテレータを返す
- それをそのまま渡すようにする
libのシグニチャを更新する:
env::Args
を型に取り- かつ、所有権を奪い、繰り返しを行うので、可変
mut
にする
本体を更新する:
添え字アクセスをやめ、添え字の場合の長さのチェックをやめ、next
メソッドを使い、Some
/None
でチェックする
13.4.2
イテレータアダプタメソッドを使用すれば、可変な状態の良を最小化し、(イテレータアダプタとそのやっていることの間隔を掴めば、)コードが明瞭化され、ループの高難度の目的に集中できる。
いろんなループを少しずつ弄んだり、新しいベクタを作ったりということをしなくて済む。
イテレータアダプタメソッドを使うか、ループを使うかは、スタイルやパフォーマンスの問題がある。