Rust演習の記録
Rust備忘録
2025/8/20 (2025/4/23作成)
- JavaScript/TypeScriptしかまともに書けない状況の打破
- 学部授業レベルでCは書いてたけど、低レイヤーのことを学ぶきっかけがほしい
- WebAssemblyにもつながる
あたりをモチベにRustの勉強をしてます。
変更履歴
- 2025/04/23 (作成): The Rust Programming Language 日本語版を12章まで読んだり写経したりそれを改造したりしたところ。ここまでで区切りにして、アウトプットとしてなにかツールを作ろうとしているので、一旦ここまでで公開します。
- 2025/7/14: 13章の所感を追加
- 2025/8/20: 14章の所感を追加
- 2025/10/dd: 15章の所感を追加
やってて色々わかんなくなったら、また立ち返って追記/修正していく予定。
アウトプット
手元で作成したものは、jonnity/rust-exerciseにコミットして、公開してます。
へ~って思ったとこ
2章. 数当てゲームのプログラミング
let guess: u32 = guess.trim().parse().expect("Please type a number!");みたいな、変数名使い回す (shadowing) 書き方ありなんだ- 型付けがしっかりしてるのと、再定義を許可することって両立するんだね
- 可読性とかの話としては同列に感じる (=再定義を乱用したら、意味わからんコードは書ける) けど、メモリ管理の観点からしたら、全然別の話ってことなのかも
3. 一般的なプログラミングの概念
constってそりゃ定数を表すよね- Javascript/Typescriptの、再代入を許さないだけのものを
constで扱うの、なかなかな気がしてきたね
- Javascript/Typescriptの、再代入を許さないだけのものを
- 言語仕様として
letで定義する変数はグローバル変数で定義できないという縛りがあるの、いいですね。- こういう仕様ってshadowingとは違って、可読性のためのものなのか?メモリ安全性にも寄与する?
- 型付けが盤石なら、メモリ的にはグローバルでも一定だから、可読性のためなのかな
- 整数の基準型は
i32、浮動小数点の基準型はf64なんだ- それぞれ↓の記載はあったけど、なんでなんだろ
- 「64ビットシステム上でも、 この型 (
i32) が普通最速になります。」 - 「なぜなら、現代のCPUでは、(
f64が)f32とほぼ同スピードにもかかわらず、より精度が高くなるからです。」
- 「64ビットシステム上でも、 この型 (
- それぞれ↓の記載はあったけど、なんでなんだろ
- ↓の記述 (Rustが配列の添字が適切かどうかをチェックするという話) は、他の言語だと実現が難しいんかな
- 実行時の動作だから、なにもしないほうが楽というのはそりゃそうなんだろうけど、やってやれないことはない話?
低レベル言語の多くでは、 この種のチェックは行われないため、間違った添え字を与えると、無効なメモリにアクセスできてしまいます。
- 仮引数 ("parameter") と実引数 ("argument") って概念、確かに両方とも引数って読んじゃうから意識的には区別できてなかったな
- この辺、言語作るとかの水準の話しなければ、みんなそんなもんなのかしら
- ブロックの中 (最後?) の行にセミコロンを含むかどうかで、その行が式か文かが決まって、それによってブロック自体も式か文か決まるってこと?わかりにくくない??
- やってたら慣れてきた。最後だけだし、まあ大丈夫な感じする
ifを式とすれば、三項演算子っていらないのか- 三項演算子的な書き方をしたいけど、わかりにくいよな~ってときもままあるから、これはいい仕様に思える
4. 所有権を理解する
- 所有権って何?と思ってたけど、「メモリのヒープ領域の管理のための仕組み」だという原理のとこから説明してもらえるから、やっぱこういう公式のドキュメントは読むべき
- 所有権という概念を成立させたまま、参照を使うとやりやすい、という説明の流れで、だいぶやりたいことがわかった気がする
- とはいえ、理解が浅いからあんまなんも言えんかも
5. 構造体を使用して関係のあるデータを構造化する
- ユニット様構造体、トレイトも何もわかってないからあれだけど、単にメンバを持たないクラスのインターフェースに使うみたいなこと?この辺に当てはめて考えるとわからんくなるんかな
- あとはまあ、そういう書き方なのね、という感じ
6. Enumとパターンマッチング
- typescriptの
enumの型がゆるゆるだって話を踏まえてみるとすげーって感じ if let、この動作をif letで書くの、いまいちよくわかってない- 動作がよくわかってないのか、英語がよくわかっていないのか
- Rustの「if let」とは何なのか?@Qiitaのおかげでだいぶ飲み込めた。The bookの例が悪い
7. 肥大化していくプロジェクトをパッケージ、クレート、モジュールを利用して管理する
- nodeの
package.jsonと比べて、デフォルトのcargo.tomlはシンプルでいいよね:src/main.rs/src/lib.rsがcrate rootになるよって話に関連して - 標準非公開なの、いっぱい
pubって書くことになって大変な気もするけどそんなもん?- むしろデフォルト
publicなTypescriptが変な気もするね - enumの要素はデフォルト
pubらしいし、まあそんなもんか
- むしろデフォルト
- あとはまあ、書き方って感じ
8. 一般的なコレクション
Stringとstrは、所有権がうまいことなるように実装されてるよってくらいしか理解できんかった- それで困らないんだろうな、という感じはする。困ったら困ったときにもう一回見よ
- 普通にUnicodeとかのことももうちょっと知っておきたい気もする
- そもそも興味あるっちゃある
or_insertがその値への可変参照を返すのなんなん?わけわからんくならん?
9. エラー処理
9.3. panic!すべきかするまいかで、2. 数当てゲームのプログラミングで作ったものの改良が提案されて、それを実装しようとしたら、モジュールシステムがあんまり理解できていないことがわかったmodはブロックを取るかどうかで、モジュールの構成も、モジュールの読み込みもどっちもやる予約後なんだね
- あとは、
try/catchでできるようなことは、Resultとmatchとかでできるよって感じに理解
10. ジェネリック型、トレイト、ライフタイム
孤児のルール(orphan rule)(外部の型に外部のトレイトを実装できない: 型とトレイトのどちらかはローカルにないといけない) で、二重定義が防げてるのいいっすねlargest()でCloneもCopyも不要にする書き方は、↓みたいなのでできた。listで受け取ったVectorの参照から、その値の参照を取り出して最大値を見つけて、その参照をそのまま返せば、largest内で値そのものを扱ってないからOKってことかも
1fn largest<T: PartialOrd>(list: &[T]) -> &T {
2 let mut largest = &list[0];
3 for i in 0..list.len() {
4 if &list[i] > largest {
5 largest = &list[i];
6 }
7 }
8 largest
9}
- 関数のライフタイム注釈って、呼ぶ側で指定することもあるのかな?
- 型注釈でも、渡した変数の型で指定するだけだったりするし、そんなもんか
- ライフタイムという概念があるということは理解した
- あとは書いて、エラーがでたらまた考えよう
- 概念もわからんと、エラー内容がわからんすぎるのでね
11. 自動テストを書く
- 言語仕様としてテストがあると、非公開関数のテストもできるっちゃできるのか
- 基本的には要らない?気はするけど、そのへんの考え方わかんないや
12. 入出力プロジェクト:コマンドラインプログラムを構築する
- 標準エラー出力って概念あるの初めて知った
- 確かにエラーメッセージがリダイレクトされても困るのか
- そうなってるツールは使ったことあるかもしれんけど、全然意識できてなかった
- CLIツール周りの話は、
main.rs/lib.rsの組み合わせが実際どうなるのかがイメージついて、だいぶ雰囲気掴めてきた感じする
13. 関数型言語の機能:イテレータとクロージャ
- イテレータはnotion-db-cliでも使ってるからまあだいたい理解できてる
- CSVのレコード読むとこで、自前で
Iteratorを実装したりもしてるし
- CSVのレコード読むとこで、自前で
- クロージャは、イテレータの関数 (
mapとか) の引数としては使ってる - notion-db-cliでも結構
cloneしてるから、もっとうまいことイテレータを使えるのかもしれない - ちゃんとイテレータも速く動くってんならいいよね
14. CargoとCrates.ioについてより詳しく
Cargo.tomlで設定するプロファイルとしては、devでopt-level = 0、releaseでopt-level = 3がデフォルトなら、とりあえず何にも設定しなくていい?- Cargo ReferenceのProfilesを見ると、debug情報を出せたりもするらしい。使わざるを得なくなるときもあるのかも
- コメントとドキュメンテーションとテスト (ドキュメントが古くなってしまうのを防ぐ意味のほうが強いかもだけど) が兼ねられるの、すごく合理的な感じする
- Cargoのサブコマンドの追加とかって何に使われるんだろ
- なんでもできる?からこそ難しそう
- cargo-scriptっていう、Rustのコードをスクリプト言語っぽく実行できるサブコマンドを追加するCrateあった。へ~
15. スマートポインタ
Boxは (所有権で確認して) 誰も使わなくなったら自動的にfreeしてくれるmallocという理解でそんなにズレてなさそう (Chat GPTも同意してくれた)- そんな感じだから、ヒープ領域に向けたポインタ的な動作をしてほしくて、それを実現してるのは
Derefトレイトを実装してるから - そんで自動で
free相当のことをしてくれるのは、Dropトレイトを実装してるから - こういう、プラスアルファの要素があるポインタをスマートポインタと呼称するっぽい
Rc<T>は参照の数を数えてくれて、複数で所有できるから、グラフで複数のNodeとのつながりを表現するときに、同一Nodeとのつながりを持つ複数のNodeで該当のNodeを所有できるみたいな使い方ができるらしい。なんとなくしかわからんのは、そもそも所有権の考え方の理解が甘いからかな……- 内部可変性は基本的にはテストに便利っすよって感じなんかな
RefCell<T>自体は、保持する値に対して借用、可変借用を提供するだけ (所有権のチェックは実行時に行う) のスマートポインタ- そんで
Rc<T>が不変参照しか取り出せない代わりに共有所有権を持たせられるスマートポインタ - →
Rc<RefCell<T>>の形で使うことで不変値扱いで読み出しを可能にしつつ、内部のRefCell<T>から可変借用をもらって、値を書き換えることができる - → それ故テスト用に使うと、テスト対象のモジュールは普通に読み出せてると思い込んでるけど、中身をテスト用の値に書き換えたりできる……ってことですか?
- 木構造で親の情報を持たせたいときなどに循環参照じみたことはさせたいことはある。たしかに
- ただ、循環参照していると参照がなくなって
Dropするって動きがなくなって、メモリリークが発生しうる Rc::downgradeで弱い参照 (Weak<T>) を扱えて、Rcは、弱い参照のカウントが0でなくてもDropするようになっているので、これなら循環してもメモリリークは起きない- その代わりに、参照先が失われている可能性があるので、
Weak<T>のupgradeを使って返ってくるOption<T>をハンドリングする必要がある
- ただ、循環参照していると参照がなくなって