導入
C言語の悪名高いバグに「ダングリングポインタ」があります。すでに解放されたメモリを指し続けるポインタで、それを使うと解放後の領域を触ってしまい、深刻なバグになります。Rust は、この危険なコードをそもそもコンパイルさせません。
説明
「解放済みのデータを指す参照」を作ろうとする、危ないコードを考えます。
fn main() {
let ref_to_nothing = dangle();
}
fn dangle() -> &String { // String への参照を返そうとする
let s = String::from("hello"); // s は関数内でヒープを確保
&s // s への参照を返す……
} // ← ここで s がスコープを抜け、ヒープが解放される!
// 返した参照は「解放済みのデータ」を指すことになる → 危険
dangle の中で作った s は、関数が終わるとスコープを抜けて解放されます(所有権ルール3)。なのに &s(s への参照)を返してしまうと、呼び出し側はすでに解放されたヒープを指す参照を受け取ることになります。これがダングリング参照です。
C言語ならコンパイルが通ってしまい、実行時に運が悪ければクラッシュ、運が良い(悪い)と一見動いてしまってセキュリティホールに——という悪夢のバグです。
Rust のコンパイラは、これをコンパイル時に検出して拒否します。
error[E0106]: missing lifetime specifier
--> src/main.rs
| fn dangle() -> &String {
| ^ expected named lifetime parameter
= help: this function's return type contains a borrowed value,
but there is no value for it to be borrowed from
コンパイラは「借用した値を返そうとしているが、その借用元がどこにも残らない(=解放される)」と見抜き、ビルドを止めます。参照が、それが指すデータより長生きすることは絶対に許さない——これが Rust の鉄則です。
正しい解決は、参照ではなく所有権ごと返すことです。
fn no_dangle() -> String {
let s = String::from("hello");
s // 参照ではなく s 自体(所有権)を返す → ムーブ
} // 所有権が呼び出し側へ移るので、ここで解放されない。安全
s を(参照ではなく)そのまま返せば、所有権が呼び出し側へムーブし、データは生き続けます。解放されるのは、新しい所有者がスコープを抜けるときです。
では、「参照が指すデータより長生きしないこと」を、コンパイラはどうやって追跡しているのでしょう? その仕組みの名前が ライフタイム(lifetime) です。次章で正体を見ます。
試すには
dangle のようなコードは Rust では書けません(コンパイルエラー)。エラーメッセージに lifetime(ライフタイム)という言葉が出てくることに注目してください。次章で、この「参照の有効期間を追跡する仕組み」を学びます。ここまでで所有権の全体像はほぼ揃いました。