100 exercise Rust
4-4
4-3ではimpl
を使って手書きで演算子をオーバーロードしてきた。しかしこの方法は変化に弱い。例えば新しいフィールドが追加されるたびにeq
メソッドを書き直す必要がある。そこでもっといい方法がある。それがderive
だ。derive
を使うとマクロを生成することができる。マクロはコンパイル時にRustのコードに書き換えられるので、4-3で書いたのとほぼ同等のコードを自動で生成する。もちろんフィールドに何が含まれているかも検出するので、コードの変化にも柔軟に対応する。下のコードはderive
を使ってPartialEq
を付与したコードと、コンパイル時に展開されるコードだ。4-3で頑張って書いたコードがたった一行で再現できる。
#[derive(PartialEq)]
struct Ticket {
title: String,
description: String,
status: String
}
#[automatically_derived]
impl ::core::cmp::PartialEq for Ticket {
#[inline]
fn eq(&self, other: &Ticket) -> bool {
self.title == other.title && self.description == other.description
&& self.status == other.status
}
}
解答
#[derive(Debug, PartialEq)]
struct Ticket {
title: String,
description: String,
status: String,
}
4-5 トレイト制約
ここではジェネリクス型を学習する。ジェネリクス型とはその名前の通り型の一種だ。しかし特定の型を指しているわけではない。いわば型に対する変数のようなものだ。関数を定義するときu32やStringなど具体的な型ではなく、ジェネリクス型を使用することで、関数を一度定義するだけで様々な型ででも使えるようになる。
トレイト境界を書かなくてコンパイラーは推測できるらしい。しかし変数に型をつけることでRustに恩恵を受けるように、トレイト境界を明示することで恩恵を受けているそうだ。
4-6 str型 ??????????
解答がわからない。なぜ返り値の型を&str
にしただけで返り値もその型に合わせられるのか。
↓
4-7に書いてある。
解答
pub fn title(&self) -> &str {
&self.title
}
pub fn description(&self) -> &str {
&self.description
}
pub fn status(&self) -> &str {
&self.status
}
4-7 参照解決型変換(Deref coercion)
なんもわからん
4-6で詰まっていたことがある。
self.title
はString型
&self.titleは
&String型 なのに返り値は
&str型だった。これはなぜか。
Deref`トレイトというのが関係しているらしい。
でもあまり使いすぎない方がいいらしい。
4-9 トレイト実装
トレイト実装がわかってない。<>の中に型を入れるのはなぜ?トレイト境界らしいが。。
トレイト実装をするときに、
トレイト・・・ある動作に関連するメソッドを列挙したもの。シグネチャが列挙されてる。
故に、trait
で定義しているメソッドはちゃんとimpl内で定義しなければならない。これが問題のヒント
FromとIntoは全然わからん
4-10 型関連とジェネリック型の使い分け
- 型関連は型パラメーターが従属しているときに使う。また、従属している型パラメーターが1つの場合に使う。
- ジェネリック型は???
To recap:
Use an associated type when the type must be uniquely determined for a given trait implementation.
Use a generic parameter when you want to allow multiple implementations of the trait for the same type, with different input types.
サイトでは丁寧に説明してたからもう一回読む
問題もマクロを使って解くものだから練習にいい
4-12 Copyトレイト
コピートレイトを実装してはいけない主なパターンは &mut
が使われてるときと、データがヒープにあるとき。前者は、借用のルールである「可変参照は2つ以上存在することができない」を破ってしまう恐れがあるから。後者は、同じアドレスを指すファットポインタが2つできてしまい、ダブルフリーを起こす可能性がある。例えば同じstrを指すStringが2つ存在し、コピー元とコピー先がdropをするとダブルフリーを起こす。こういう理屈知らなかったから勉強になった。
この理屈が大事そうだから後からよめ
That's not all, though. A few more conditions must be met:
The type doesn't manage any additional resources (e.g. heap memory, file handles, etc.) beyond the std::mem::size_of bytes that it occupies in memory.
The type is not a mutable reference (&mut T).
しかし問題が全然わからない。
4-13
p278
問題が何をやっていいかすらわからなかったのでまたやる
impl
でdrop
関数を実装しても、値をドロップする挙動は変わらず、ドロップされる際(直前か直後かは知らない)の動作をカスタマイズすることができる
4-14 Outro
大事そうなこと
Don't make a function generic if it is always invoked with a single type. It introduces indirection in your codebase, making it harder to understand and maintain.
Don't create a trait if you only have one implementation. It's a sign that the trait is not needed.
Implement standard traits for your types (Debug, PartialEq, etc.) whenever it makes sense. It will make your types more idiomatic and easier to work with, unlocking a lot of functionality provided by the standard library and ecosystem crates.
Implement traits from third-party crates if you need the functionality they unlock within their ecosystem.
Beware of making code generic solely to use mocks in your tests. The maintainability cost of this approach can be high, and it's often better to use a different testing strategy. Check out the testing masterclass for details on high-fidelity testing.
あと練習問題は無水のでやっておこう
5-3
- panic!からResult型に変換する方法がわからなかった
- statusの型をStatusにしているので、newメソッド内では"Done", "ToDO", "InProgress"のいずれかであることが保証されている。もちろんこの3つは文字列ですらない。
Done
型、ToDo
型,InProgress
型として定義されている。Rubyのenumだと数値と値が対応しているから違和感を感じるが、Rustでは型をしていしてるだけでいい。文字列が欲しくなったら後の処理ですればいい。
https://rust-exercises.com/05_ticket_v2/03_variants_with_data
5-4 if let
-
if let
やif else
は分岐が1つの時に使うものであり多用するものではない。match
を使うのが多い - match した値の一部を取り出すのは「参照パターン」とか「ref パターン」いうもの(プログラミングRust p.222)。参照で受け取っているから参照外しをする必要がある。
- 上級っぽい内容なのでまたいつかレベルアップしたら取り組む
https://rust-exercises.com/05_ticket_v2/04_if_let
5-5 Nullability
-
Option
型を返り値とするとき、Some
とNone
の2パターンに値を入れて返す。 -
&self.status
のように参照のフィールドの型は参照 -
null
のパターンを言語レベルで実装しているのでreturn if user == null
みたいな定型文のアーリーリターンを書かなくていいのが良いな。nullチェックが見栄え良くなる。
5-6 Fallability
- Result型はユーザーの処理のように「実行時まで値が確定しない」場合に使う
https://qiita.com/namn1125/items/13870965e8273bc87bce - Result型を返り値にしているとき、
Ok
とErr
に引数を入れてそれを返す - Rustには例外処理はなくエラーを扱うときはResult型を使う
5-7 Unwrapping
-
Result
型はエラーケースを無視することができないので、match
パターンや.unwrap()
を用いてエラーでないことを確認する必要がある。 - エラー処理は呼び出し元で行う必要がある
問題を復習する
5-8 Error enums
- Errorをenumにするのはわかるが、引数を入れてもいいのはへーっと思った。エラーの文言がなけりゃ原因がわからないし、全てのエラーに対してヴァリアントを作るより、カテゴリ分けして詳細はエラーメッセージとしてStringに代入するのがいい
- エラーメッセージを表示するときの書き方が5-9でやるので鵜呑みにしない
これも復習
Error Trait
- Errorトレイトを実装するのは初めてだから全然わからなかった。ErrorはOptionでのみ使うと思っていたがそうではない。
- プログラミングRust p151に同じのがある
- これみたいに一から実装するのではなく、
thiserror
というクレートを書くと宦官にかける。でも知識として頭に入れておきたい。
むずいからまたやる
だんだん飽きてきた。練習問題ばっか解くのは苦痛
5の残りはスキップして6に映る
この問題集を作った人が書いた『Zero to Production in Rust』に移りたい。
解答とか
7-1 Threads
- スライスは
Vec
または配列の一部分を指定するもの。アドレスと長さをもつファットポインタでデータを持たない。