Rustに入門する〜4日目 リファクタリング〜


# 初めに

3日目の続き、
直さないといけないのはわかっていたけど動くからで放置していたものをきれいにしていきます。

ちなみにこの記事を書いている途中で知ったのですが、
今まで読んでた公式のガイドは古いバージョンだったみたいです...
Url親のたどったらなんか出てきた...
https://doc.rust-jp.rs/book/second-edition/

# `&str`と`String`どっちを使うか

C言語はある程度やっていたので`str`と`&str`については言わずもがなという感じでしたが、
`String`という概念はなかったのでさて`&str`と`String`はなんだとなるわけです。
それこそ`String`と`char`の概念は色々な言語にそれぞれあるかと思いますが
`&str`を含む３種類を持つ言語は多分これが初めてです。

> 文字列
> http://doc.rust-jp.rs/the-rust-programming-language-ja/1.6/book/strings.html


またこれを理解するにあたってヒープの文字が見えたのでスタックとヒープの記事を事前に読んでおきました。
４章まで読めばとりあえず書けそうって思ったので実は５章以降読んでないのを忘れてました。

C言語を昔趣味でやってた時なんとなくこれらは違うくらいで、
スタックは早い！ヒープは動的なものが入るメモリ領域であまり使わない方が良い。
くらいの認識でしっかり理解はできていなかったのでこの機会に少し踏み込みましょう。
(徐々に確保領域が大きくなる`malloc`と`free`をループさせてプログラムが死んだ...なんて思い出が)

> スタックとヒープ
> https://doc.rust-jp.rs/the-rust-programming-language-ja/1.6/book/the-stack-and-the-heap.html

上記以外も諸々見ていますが、ざっくりの違いは以下でした。
(他にも僕が情報源として見ていないだけで色々あると思いますが)

- 可変長であるか(`String`)、固定長であるか(`&str`)
- 変更可能であるか(`String`)、変更不可であるか(`&str`)
- 借用となるか(`String`)、参照となるか(`&str`)

可変長ということは伸縮のための仕組みを持っているはずなので`String`はコストが高そうです。
特に伸長は、一回確保した後連続する領域で確保できない場合のコストが凄そう。

また、引数で渡す際に`&str`は参照として引き渡せる点も大きい...と思ったんですが
たぶん`&String`で渡せば解決する問題ですね。

またこれに加えそれぞれの型変換を行う際には以下のようになるようです

- `&str -> String`の変換はメモリ確保のコストがかかる
    - 元の文字列の複製を行う為
- `String -> &str`への変換はほぼコストがかからない
    - `&str`は`String`で確保された領域を参照で挿すだけ？

上記の記事にもありましたが文字列を定義する場合`&'static str`な点を含めると、
通常参照のみであれば`&str`を使うべきでしょうか。

> "Hello there." は文字列リテラルで、 &'static str 型を持ちます。

ただ`&str`の場合はあくまで参照を返すはずなので、
関数の返り値などは`String`として返さないと実態がないことになるので(rust的にはそもそもコンパイルエラーが出そうだが)、
諸々踏まえ使えそうであれば`&str`として使っていくことになるでしょうか

`Deserialize`した値を入れる先に参照型があると死にましたがこれも多分同じ理由ですね。
`fn main()`で`fn a()-> &str`を呼び出すなんてことがもしできたら、
aの関数の中で確保した文字列の参照を返したとしても、aの関数の文字列が終了した途端にその参照元は消えるわけですからね。
(詳しくは上記のスタックとヒープ)

# `await?`って何

あちこちで出てきたんですがとりあえず`unwrap()`でもなんか知らんけどいけるやんけ！って放置してました。
`.await.unwrap()`でやってるとチェーンが長くなるのでサンプル通り`.await?`を理解して使えるようになります。

公式のガイドだと以下のページに載ってました

> エラー委譲のショートカット-演算子
> https://doc.rust-jp.rs/book/second-edition/ch09-02-recoverable-errors-with-result.html

どうやらこれを使うと`Result<T,E>`で返してくれるようです。
今まで`unwrap()`を使っていたのはエラー処理に気を使わず動かすために...というよりとりあえず実装お手軽！と思ってやってました。
`Result`だとmatchで処理しないといけないし...と思っていたのので。
つまり今までは`unwrap()`でエラーなら`panic!()`で落ちるという処理を`Err(e)`として返却してくれるという形で使えそうです。

さて、改めて理解したところで、
`Result<T,E>`は今までunwrap()してきたところを丁寧に理解して戦う必要がありそうです。

# `Result`型への理解を深める

`Result<T,E>`は今まで`unwrap`するか`match`させてエラーを出力するだけだけだったのでしたが、
返り値として返そうとするとめちゃくちゃ大変でした。

`fn a()->Result<T,E>`を定義する際に`T`については本来返そうとしている型を入れるだけでしたが、
`E`にも適切な型を当てなければいけないのですがこれが厄介です。

例えば`reqwest::ClientBuilder::new().build()?`とするだけの関数であれば
`E`には`reqwest::Error`を設定すれば良いのですが、
例えばこの前後にファイルを開きつつ`?演算子`で処理しようとすると`io::Error`が発生する可能性があります。
なので`E`には`reqwest::Error`と`io::Error`の両方が入るようにしないといけないみたいです。

これを解決するための方法はいくらかあるみたいです。

- エラーハンドリングをこなし返却されるエラーの方が一定になるようにする
- `failure`等ライブラリを使う
    - どのライブラリが最適かは結構変わっているっぽい？
- 複数のエラー型を含む独自のエラー型を定義する

なお今回は上記の立ち向かう選択肢を取らず逃げる選択肢を取ろうと思います。
つまり、
`reqwest::Error`については一時的な通信エラーの可能性があるので`?演算子`を利用し、
`serde_json::error:Error`については構造による永続的なエラーの可能性が高いため`unwrap()`とする。
ということにします。

こうすることでこの関数のエラーの返却は`reqwest::Error`のみとなりました。
(いつか正式に対応します...)


# JSONのデシリアライズの際にkey名を指定する

`serde_json`の機能というより`Serialize`/`Deserialize`の機能であるみたいです。

> Field attributes
> https://serde.rs/field-attrs.html

こんな感じのJSONがあったときに

```json
{
    "dateTime" : "xxxxx"
}
```

以下の定義をして`let sc = serde_json::from_str(&json).unwrap();`のような感じでデシリアライズをすると、
`sc.dateTime`ではなく`sc.date_time`として呼び出せます。

```rust
pub struct SampleClass{
    #[serde(alias = "dateTime")]
    pub date_time: String
}
```

# 4日目を終えて

さくっと軽く綺麗にしてと思ったら`Result`の部分が想像異常に重かったです。
お試しでプログラム作ってみたいよ！って人は`Result`型に関しては脳死で`unwrap()`した方がいいとしか思えなかった。
よくはないですが、こういうとりあえず動く部分に影響しないところはなかなかモチベーションが上がらない。
(特にRustで初めて作っている部分でもあるので)

次回はSlackのAPIを叩く予定ですが、
3日目のカレンダーから情報を取る際に終日の取れ方がなんか変という点の解消や所用で少し間が開くと思います。