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 にして輪を断ち切る!

  • forgetleak は、FFIやグローバル設定の初期化以外では使わない!

  • 無限ループ の中で Vec にデータを詰め込み続けない!

「安全な言語」と言われる Rust で、あえてメモリを漏らして遊ぶ……これこそハッカーの醍醐味だね(笑)。

次は、このリークを検出するためのツール(ValgrindやLsan)を Ubuntu で動かしてみる?それとも、逆に「1バイトも無駄にしない極限の省メモリ設計」を攻めてみる?😊✨