🗝️

所有権 — RubyのGCがやってくれていたことを自分でやる

Rubyistが最も苦戦するRustの核心概念

Rubyでメモリ管理を気にしたことがあるだろうか?おそらくない。GCが全てやってくれるから。オブジェクトを作り、変数に入れ、メソッドに渡し、使わなくなればGCが片付ける。

RustにはGCがない。代わりに所有権という3つのルールでメモリを管理する。

ルール1:全ての値には所有者が1つ

let s1 = String::from("hello");
let s2 = s1;  // s1の所有権がs2にムーブ
// println!("{}", s1);  // コンパイルエラー!s1はもう有効でない

Rubyでs2 = s1すると両方とも同じオブジェクトを指す。両方使える。Rustではs1が消える。s2だけ残る。

これがRubyistにとって最もショッキングな部分だ。「変数に入れただけなのに元が消える?」

なぜこうなっているか

RubyのGCはランタイムで「このオブジェクトをまだ誰かが使っているか?」を追跡する。これがCPU時間とメモリを消費する。Rustはこの追跡をコンパイル時に行い、ランタイムコストをゼロにする。

ルール2:借用(borrow)— 参照で渡す

所有権を渡さずに貸すことができる。

fn print_len(s: &String) {  // &で借用
    println!("{}", s.len());
}

let s = String::from("hello");
print_len(&s);  // &で貸す
println!("{}", s);  // sはまだ使える!

&は「読むだけ、所有権はあなたが持って」という意味。Rubyではこの区別は不要だった。全てのオブジェクト渡しが参照渡しだから。

ルール3:可変借用は1つだけ

let mut s = String::from("hello");
let r1 = &s;      // 不変参照 — OK
let r2 = &s;      // もう1つの不変参照 — OK
// let r3 = &mut s;  // 可変参照 — エラー!不変参照がある間は不可

「複数人が読むのはOK、1人が書くのもOK、でも読んでる人がいる時に誰かが変更するのはダメ」。このルールがデータレースをコンパイル時に防ぐ。

Clone — Rubyの.dup

所有権を渡したくなく借用も適切でなければコピーする。

let s1 = String::from("hello");
let s2 = s1.clone();  // ディープコピー
println!("s1: {}, s2: {}", s1, s2);  // 両方OK

Rubyの.dup/.cloneと同じ。ただし性能コストがあるので必要な時だけ。

Copy — スタック値は例外

整数、ブール、浮動小数点のような小さい値は自動コピーされる。

let x = 42;
let y = x;  // コピーされる(moveではない)
println!("{}", x);  // OK!整数はCopy traitを実装

スタックにある小さい値はコピーコストがほぼないので自動コピー。Stringはヒープデータなので自動コピーされない。

実践パターン

大抵の場合、この順番で考えればいい:

  1. 参照(&)で借用できるか? → 大抵これで十分
  2. 所有権を渡す必要があるか? → 関数が値を所有すべき時だけ
  3. cloneが必要か? → 上の2つとも無理な時

キーポイント

1

全ての値に所有者1つ — let s2 = s1;で所有権がムーブ

2

&で貸せば所有権維持 — 大抵これで十分

3

不変参照複数OK、可変参照は1つだけ — データレース防止

4

clone()でディープコピー — Rubyの.dup

メリット

  • GCなしでメモリ安全 — ランタイムコストゼロ
  • データレースがコンパイル時に検出される

デメリット

  • Rubyにない概念なので初期学習曲線が急
  • コンパイラとの戦いに最初はかなり時間がかかる

ユースケース

関数にデータを渡す時 — 借用 vs ムーブの判断 マルチスレッドコードでのデータ共有