🦀

The Rust Programing Language 2日目

2023/10/31に公開

前回のあらすじ
Rustを体系的に学ぶべく、TRPLの理解を日課にするのであった。
2日目は4章

学び

所有権

  • メモリを解放するプロセスには主に2つある
    • GCが定期的にお掃除するケース
    • 実装者が手動で解放するケース
  • Rustでは第三のプロセスを採用している
    • それが所有権システムによる管理

スタックとヒープ

  • スタックは文字通り縦に積まれていくイメージ
    • last in first out
    • 高速に取り出せる
    • スタック上のデータは全て既知の固定サイズ
  • ヒープは充分な空きがある特定のアドレスを確保して、ポインタを返却する
    • allocateする、という
    • 「メモリ」と聞いて自分が想像するのはこれ
    • レストランに来た客に人数から判断して適当なテーブルを割り当てるイメージ
    • ポインタを基にアクセスする必要があるので低速(当社比)
  • 所有権システムは、ヒープの最適化のために存在する
    • どのコードがヒープのどこを使っているか把握する
    • ヒープ上でデータの重複を最小化する
    • 未使用のデータを掃除する
  • スタックで管理されるデータ型とヒープで管理されるデータ型がある
    • Stringはヒープで管理され、スカラー型はスタック
    • 配列やタプルもスタックで、vectorはヒープ
    • ここちょっとなぜそうなっているのかまでは理解できていない

文字列

  • let mut s = String::from("hello");
  • 文字列リテラルは不変、Stringは可変化可能
    • リテラルはコンパイル時に中身が判明している
      • 故にバイナリにハードコードされる
    • Stringはコンパイル時には不明な量をヒープに確保する
      • 実行時にOSにメモリを要求する
        • String::from()した時
      • 使用後、OSにメモリを返す必要がある
        • スコープを抜けたタイミングで自動開放される
        • 内部でdropという関数が呼ばれる

ムーブ

let x = 5;
let y = x;

これはそれぞれがスタックに積まれるので、x=5,y=5として成立する。

let s1 = String::from("hello");
let s2 = s1;

これはs1が無効になる。
所有権が移るとか、ムーブするとか言うらしい。
要するに他言語で言うshallow copyのような挙動になるのだが、同じメモリを参照していると自動開放時に二重開放になってしまいエラーとなるため、それを防ぐために2つのポインタが同じメモリを指す状態を防いでいると言うことらしい。
また、自動的にdeep copyされる事がないので、コピーの実行性能が良い。

  • clone()deep copyが可能
  • スタックされる型のコピーはdeep copy
  • Copyトレイトを実装している型は、代入しても元の値を使用できる(ムーブしない)
    • タプルは中身が全てCopyならCopy
  • 関数に変数を渡したときも、関数にムーブすることになる
  • 関数の戻り値を変数に代入するのも、戻り値が変数にムーブしたことになる
    • これは別にそんなに意識する必要なさそう

参照と借用

  • 関数に値を渡すとムーブするので、その値をあとで使うためには毎回その値を返す必要がある
    • それはめんどくさいので、参照を取る
    • 変数名の前に&をつけることで、所有権を受け取ることなく参照できる
    • 関数に参照を渡すのを借用と言う
    • 借用したものは変更できない
      • &mutにすることで参照を可変にできる(参照元がmutの場合)
        • 特定データの可変参照は1つしか存在できない
          • これ聞いたことあります
          • データ競合するからダメと言う話
        • 不変参照と可変参照は同時に存在できない
          • 不変で参照してる間に変えられると困るため
    • 参照は常に有効である必要がある(無効になる参照を渡すとコンパイルエラーになる)
    • 所有権を渡す必要がない時は基本的に参照を渡すようにするべきと理解

スライス

  • let slice = &s[0..2];で、スライスの参照を得られる
    • Pythonと似た感じだけど明確に異なる
      • 型が違う。&strと書く
      • スライス開始地点と、要素数を持っている
      • sに対する不変参照なので、これを取ってからsが変更されない
  • 文字列リテラルは実はスライス
    • let s = "Hello, world!";した時、sの型は&strになる
  • Stringの参照を引数に取るなら、代わりにスライスを引数に取る
    • そうするとスライスがある時はスライスを、なければ文字列全体のスライスを渡せる
  • 配列のスライスをとる事もできる

まとめ

所有権についてはよく難しいと聞くので身構えてたが、そんなに抵抗なく受け入れられた。
これが実際に開発になった時にどう苦しむのかというのが楽しみ。
むしろスタックについてよくわかってなくて、スタックがなぜ早いのかみたいなところを把握するのに時間がかかった。
CSって大事ですね。

次は5章、structでデータをグループ化するらしい。初耳ワード。

Discussion