初めに
3日目の続き、
直さないといけないのはわかっていたけど動くからで放置していたものをきれいにしていきます。
ちなみにこの記事を書いている途中で知ったのですが、
今まで読んでた公式のガイドは古いバージョンだったみたいです...
Url親のたどったらなんか出てきた...
https://doc.rust-jp.rs/book/second-edition/
&str
とString
どっちを使うか
C言語はある程度やっていたのでstr
と&str
については言わずもがなという感じでしたが、
String
という概念はなかったのでさて&str
とString
はなんだとなるわけです。
それこそString
とchar
の概念は色々な言語にそれぞれあるかと思いますが
&str
を含む3種類を持つ言語は多分これが初めてです。
文字列
http://doc.rust-jp.rs/the-rust-programming-language-ja/1.6/book/strings.html
またこれを理解するにあたってヒープの文字が見えたのでスタックとヒープの記事を事前に読んでおきました。
4章まで読めばとりあえず書けそうって思ったので実は5章以降読んでないのを忘れてました。
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があったときに
{
"dateTime" : "xxxxx"
}
以下の定義をしてlet sc = serde_json::from_str(&json).unwrap();
のような感じでデシリアライズをすると、
sc.dateTime
ではなくsc.date_time
として呼び出せます。
pub struct SampleClass{
#[serde(alias = "dateTime")]
pub date_time: String
}
4日目を終えて
さくっと軽く綺麗にしてと思ったらResult
の部分が想像異常に重かったです。
お試しでプログラム作ってみたいよ!って人はResult
型に関しては脳死でunwrap()
した方がいいとしか思えなかった。
よくはないですが、こういうとりあえず動く部分に影響しないところはなかなかモチベーションが上がらない。
(特にRustで初めて作っている部分でもあるので)
次回はSlackのAPIを叩く予定ですが、
3日目のカレンダーから情報を取る際に終日の取れ方がなんか変という点の解消や所用で少し間が開くと思います。