Prompt
Rustでメモリリークを”起こす”方法
あはは、最高にパンクな質問だね!「どうすれば壊れるか」を知ることは、情報工学において「どう守るか」を知ることと同じくらい重要だぜ。
Rustは「メモリ安全」を謳っているけど、実は**「メモリリークさせないこと」は保証の対象外**なんだ(公式ドキュメントにも明記されているぜ)。
Rustで「意図的に」メモリを食いつぶす、あるいは解放できなくさせる代表的な「地獄のレシピ」を3つ紹介するよ。
1. 💀 禁断の儀式:std::mem::forget
一番手っ取り早く、かつ「確実」な方法だ。
Rustには、変数の所有権をシステムから隠蔽し、スコープを抜けても Drop(デストラクタ)を呼ばせない 魔法の関数がある。
Rust
let v = vec![1, 2, 3];
std::mem::forget(v); // v の所有権は永遠に彷徨い、メモリは解放されない
💡 豆知識
これは内部的に ManuallyDrop という型を使っているんだ。本来は「FFI(他の言語とのやり取り)で、C言語側にメモリ管理を任せるとき」なんかに使う真っ当な機能なんだけど、悪用すれば立派なリーク兵器になるぜ。
2. 🌀 無限ループの罠:Rc<T> による循環参照
さっき「地獄の相互参照」の話をしたけど、あれこそが Rust で起きる最も「自然な」メモリリークだ。
Rc(参照カウンタ)は、カウンタが 0 になるとメモリを解放する。でも、AがBを指し、BがAを指していると、お互いにカウントを 1 以上に保ち続けてしまうんだ。
Rust
use std::rc::Rc;
use std::cell::RefCell;
struct Node(Option<Rc<RefCell<Node>>>);
let a = Rc::new(RefCell::new(Node(None)));
let b = Rc::new(RefCell::new(Node(Some(a.clone()))));
// a の中に b を入れる → 循環発生!
a.borrow_mut().0 = Some(b.clone());
// スコープを抜けても、お互いがお互いを参照しているのでカウンタが0にならず、リークする。
3. 🏗️ ヒープの不夜城:Box::leak
「このデータ、プログラムが終わるまでずっと生きててほしいんだよね」という時に使う。
名前の通り、明示的にメモリをリークさせるメソッドだ。
Rust
let s = String::from("永遠の命");
let static_ref: &'static str = Box::leak(s.into_boxed_str());
Box::leak を使うと、ヒープ上のデータの所有権が放棄され、代わりに 'static な参照が手に入る。これは一度呼ぶごとに、そのメモリはプロセスが終了するまで二度と再利用されない。
⚠️ 見落とされがちな視点:unsafe は不要?
ここが面白いポイントなんだけど、上記のコードはどれも unsafe を使っていないんだ。
Rustにおいて「メモリリーク」は “Safe Rust” の範囲内 で許容されている。なぜなら、メモリが足りなくなることは「行儀が悪い」けれど、メモリ上の不正な場所にアクセスするような「未定義動作(Undefined Behavior)」ではないからなんだ。
🏁 まとめ:リークを防ぐための教訓
-
Rcを使うときは、片方をWeakにして輪を断ち切る! -
forgetやleakは、FFIやグローバル設定の初期化以外では使わない! -
無限ループ の中で
Vecにデータを詰め込み続けない!
「安全な言語」と言われる Rust で、あえてメモリを漏らして遊ぶ……これこそハッカーの醍醐味だね(笑)。
次は、このリークを検出するためのツール(ValgrindやLsan)を Ubuntu で動かしてみる?それとも、逆に「1バイトも無駄にしない極限の省メモリ設計」を攻めてみる?😊✨