アズマオオズアリの頭部とキーボードを模したアイコンとjonnityという文字

Rust演習の記録

Rust備忘録

2025/4/23


  • JavaScript/TypeScriptしかまともに書けない状況の打破
  • 学部授業レベルでCは書いてたけど、低レイヤーのことを学ぶきっかけがほしい
  • WebAssemblyにもつながる

あたりをモチベにRustの勉強をしてます。

2025/04/23現在、The Rust Programming Language 日本語版を12章まで読んだり写経したりそれを改造したりしたところ。 ここまでで区切りにして、アウトプットとしてなにかツールを作ろうとしているので、一旦ここまでで公開します。

やってて色々わかんなくなったら、また立ち返って追記/修正していく予定。

アウトプット

手元で作成したものは、jonnity/rust-exerciseにコミットして、公開してます。

へ~って思ったとこ

2章. 数当てゲームのプログラミング

  • let guess: u32 = guess.trim().parse().expect("Please type a number!");みたいな、変数名使い回す (shadowing) 書き方ありなんだ
    • 型付けがしっかりしてるのと、再定義を許可することって両立するんだね
    • 可読性とかの話としては同列に感じる (=再定義を乱用したら、意味わからんコードは書ける) けど、メモリ管理の観点からしたら、全然別の話ってことなのかも

3. 一般的なプログラミングの概念

  • constってそりゃ定数を表すよね
    • Javascript/Typescriptの、再代入を許さないだけのものをconstで扱うの、なかなかな気がしてきたね
  • 言語仕様としてletで定義する変数はグローバル変数で定義できないという縛りがあるの、いいですね。
    • こういう仕様ってshadowingとは違って、可読性のためのものなのか?メモリ安全性にも寄与する?
    • 型付けが盤石なら、メモリ的にはグローバルでも一定だから、可読性のためなのかな
  • 整数の基準型はi32、浮動小数点の基準型はf64なんだ
    • それぞれ↓の記載はあったけど、なんでなんだろ
      • 「64ビットシステム上でも、 この型 (i32) が普通最速になります。」
      • 「なぜなら、現代のCPUでは、(f64が) f32とほぼ同スピードにもかかわらず、より精度が高くなるからです。」
  • ↓の記述 (Rustが配列の添字が適切かどうかをチェックするという話) は、他の言語だと実現が難しいんかな
    • 実行時の動作だから、なにもしないほうが楽というのはそりゃそうなんだろうけど、やってやれないことはない話?

低レベル言語の多くでは、 この種のチェックは行われないため、間違った添え字を与えると、無効なメモリにアクセスできてしまいます。

  • 仮引数 ("parameter") と実引数 ("argument") って概念、確かに両方とも引数って読んじゃうから意識的には区別できてなかったな
    • この辺、言語作るとかの水準の話しなければ、みんなそんなもんなのかしら
  • ブロックの中 (最後?) の行にセミコロンを含むかどうかで、その行が式か文かが決まって、それによってブロック自体も式か文か決まるってこと?わかりにくくない??
    • やってたら慣れてきた。最後だけだし、まあ大丈夫な感じする
  • ifを式とすれば、三項演算子っていらないのか
    • 三項演算子的な書き方をしたいけど、わかりにくいよな~ってときもままあるから、これはいい仕様に思える

4. 所有権を理解する

  • 所有権って何?と思ってたけど、「メモリのヒープ領域の管理のための仕組み」だという原理のとこから説明してもらえるから、やっぱこういう公式のドキュメントは読むべき
  • 所有権という概念を成立させたまま、参照を使うとやりやすい、という説明の流れで、だいぶやりたいことがわかった気がする
  • とはいえ、理解が浅いからあんまなんも言えんかも

5. 構造体を使用して関係のあるデータを構造化する

  • ユニット様構造体、トレイトも何もわかってないからあれだけど、単にメンバを持たないクラスのインターフェースに使うみたいなこと?この辺に当てはめて考えるとわからんくなるんかな
  • あとはまあ、そういう書き方なのね、という感じ

6. Enumとパターンマッチング

  • typescriptのenumの型がゆるゆるだって話を踏まえてみるとすげーって感じ
  • if let、この動作をif letで書くの、いまいちよくわかってない

7. 肥大化していくプロジェクトをパッケージ、クレート、モジュールを利用して管理する

  • nodeのpackage.jsonと比べて、デフォルトのcargo.tomlはシンプルでいいよね: src/main.rs/src/lib.rsがcrate rootになるよって話に関連して
  • 標準非公開なの、いっぱいpubって書くことになって大変な気もするけどそんなもん?
    • むしろデフォルトpublicなTypescriptが変な気もするね
    • enumの要素はデフォルトpubらしいし、まあそんなもんか
  • あとはまあ、書き方って感じ

8. 一般的なコレクション

  • Stringstrは、所有権がうまいことなるように実装されてるよってくらいしか理解できんかった
    • それで困らないんだろうな、という感じはする。困ったら困ったときにもう一回見よ
  • 普通にUnicodeとかのことももうちょっと知っておきたい気もする
    • そもそも興味あるっちゃある
  • or_insertがその値への可変参照を返すのなんなん?わけわからんくならん?

9. エラー処理

  • 9.3. panic!すべきかするまいかで、2. 数当てゲームのプログラミングで作ったものの改良が提案されて、それを実装しようとしたら、モジュールシステムがあんまり理解できていないことがわかった
    • modはブロックを取るかどうかで、モジュールの構成も、モジュールの読み込みもどっちもやる予約後なんだね
  • あとは、try/catchでできるようなことは、Resultmatchとかでできるよって感じに理解

10. ジェネリック型、トレイト、ライフタイム

  • 孤児のルール(orphan rule) (外部の型に外部のトレイトを実装できない: 型とトレイトのどちらかはローカルにないといけない) で、二重定義が防げてるのいいっすね
  • largest()CloneCopyも不要にする書き方は、↓みたいなのでできた。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の組み合わせが実際どうなるのかがイメージついて、だいぶ雰囲気掴めてきた感じする