← posts

Rustの所有権、3回目でやっと腑に落ちた

rust を触り始めて、最初の二回は所有権で挫折した。 三回目でやっと腑に落ちたので、何が分かっていなかったのかを残しておく。

一回目と二回目で何を間違えていたか

最初の二回、私は所有権を「ルール」として覚えようとしていた。 値はひとつの所有者しか持てない、参照は借用、可変参照は同時にひとつ──こういう箇条書きを暗記していた。

暗記したルールは、コンパイルエラーが出た瞬間に役に立たない。 なぜそのルールがあるのか分かっていないから、エラーメッセージを「禁止された」としか読めない。 禁止の理由が見えないと、回避策もその場しのぎになる。

三回目は「メモリの持ち主は誰か」から考えた

三回目に変えたのは、所有権をルールではなく「後始末の責任」として読み直したことだった。

所有権 とは要するに、そのメモリを誰が解放するかという責任の所在だ。 所有者がスコープを抜けるとき、drop が走って後始末をする。 責任がひとつに定まっていれば、二重解放も解放忘れも起きない。

この視点に立つと、ムーブが急に自然なものに見えてくる。

ownership.rs
fn main() {
    let s = String::from("hello");
    let t = s;            // s が持っていた責任が t に移る(ムーブ)
    // println!("{s}");   // ここで s を使うとコンパイルエラー
    println!("{t}");      // t が唯一の所有者
}                         // スコープ末尾で t の drop が走り、ヒープが解放される

let t = s; で起きているのは、文字列の中身のコピーではない。 String はスタック上に「ポインタ・長さ・容量」の三つ組を持っていて、ムーブはこの三つ組だけを移す。 ヒープ上の hello 本体はそのまま、責任のラベルだけが付け替わる。

ムーブとメモリ配置、そして アラインメント

なぜ三つ組だけ移せば済むのか。 それは String というスタック上の値が、固定サイズ・固定配置の構造体だからだ。

スタックに置かれる値はサイズが静的に決まり、各フィールドは アラインメント に従って整列されている。 ポインタ幅と usize が揃っているからこそ、三ワードを丸ごと別の場所へ写すだけでムーブが完結する。 所有権の移動が「軽い」のは、この素直なメモリ配置に支えられているわけだ。

逆に言えば、ヒープ本体を触らずに済むからムーブは速い。 コピーに見えてコピーしていない、という違和感の正体はここにあった。

ライフタイムは「借用がいつまで有効か」の話

所有権が腑に落ちると、ライフタイムも急に読めるようになった。 借用は所有権を奪わず、一時的に参照を貸すだけ。 ならば「貸している間に持ち主が消えたら困る」のは当たり前で、ライフタイム注釈はその「困らない範囲」をコンパイラに伝える道具にすぎない。

借用チェッカは敵ではなく、後始末の責任が破綻していないかを先に教えてくれる校正者だ。

三回目にしてようやく、エラーメッセージが「禁止」ではなく「指摘」に見えるようになった。 ルールを覚えるのをやめて、責任の流れを追うようにしたら、所有権はただの自然な帰結になった。

#rust #ライフタイム

— fin. 2026-06-26
2-HOP LINKSこの記事と語をともにする記事
見出し = この記事が張ったリンク/その下 = 同じ語にリンクする別の記事