Rustで『安全』と言い切れるか?Cloudflare 障害と unwrap() のリアル
はじめに
2025年11月18日、インターネットインフラ大手 Cloudflare にて、世界規模のサービス障害が発生しました。
その原因として、Rust 製プロキシサービス内で起きた panic の一撃が引き金になったことが報じられています。中でも注目されたのが、Rust 標準ライブラリの Option::unwrap() または Result::unwrap() の呼び出しです。
しかし、この「unwrap が原因」という一言だけで終わる話ではありません。今回は、Rust における unwrap() の意味・使いどころ・今回の障害が教えてくれたことを丁寧に振り返ります。
Rust における unwrap() とは
まず、Rust 標準ライブラリにおける Option<T> および Result<T, E> 型からおさらいしましょう。
Option/Result 型
Rust では、値が存在しない可能性やエラーの可能性を型として表現します。
-
Option<T>はSome(T)またはNoneの二択。 -
Result<T, E>はOk(T)またはErr(E)の二択です。
Rust の設計思想では、「ヌルポインタ」や「例外(Exceptions)」に頼るのではなく、処理可能な値として可能性を明示することが推奨されています。
unwrap() の振る舞い
Option<T> や Result<T, E> に .unwrap() を使うと以下のようになります:
let maybe_value: Option<&str> = Some("foo");
let value = maybe_value.unwrap(); // "foo"
let none_value: Option<&str> = None;
let value2 = none_value.unwrap(); // panic: “called `Option::unwrap()` on a `None` value”
また Result<T, E>::unwrap() は Ok(v) なら v を返し、Err(e) の場合には panic(実行中止)となります。
つまり、unwrap() は “この値は必ずある/この処理は必ず成功する” という前提のもとで使われる関数です。もしその前提が崩れた場合、プログラムは即時に終了(panic)します。
Rust の公式サイトも unwrap() による panic の可能性を明記しており、慎重に使うよう警告しています。 
Cloudflare の障害と unwrap() の関係性
ここからは、Cloudflare における障害と unwrap() がどのように関係していたのかを整理します。
障害の概要
- 2025年11月18日、Cloudflare の Bot Management 機能で「特徴量ファイル(feature file)」のレコード数が想定値を超えて急増。仕様では数十レコード想定だったところ、200〜300超に。
- 結果として、該当プロキシサービスが Rust 製であったため、内部処理で想定外のデータを扱った際に panic が発生。
- 当該サービスは世界中のエッジノードに展開されていたため、影響が一気に波及しました。
(参考記事) 
なぜ “unwrap が原因” と言われたのか
報道および技術ブログでは以下の論点が挙げられています
- Cloudflare の障害を “a single Rust .unwrap() in Cloudflare’s edge network” と表現した記事もあります。 
- もう一方では「unwrap そのものが悪ではなく、unwrap が前提としていた “必ずある” を保証できない状況だった」という議論もあります。
なぜ致命的な影響に至ったのか
複数の要因が重なりました
- 前提条件の破壊
仕様上「特徴量ファイルは数十レコード」という前提が、実際には数百レコードという異常値となっており、その変化を検知できていませんでした。 - 入力検証・防御コードの欠如
異常な入力を事前に弾く、またはフォールバックする処理が不十分だったという指摘があります。 - unwrap による panic によってプロキシ停止
外部入力に対して unwrap が呼ばれた時点で panic すると、そのノードが停止。多数ノード停止に繋がりました。 - 分散システムという特殊環境
エッジネットワークで大量ノードが同一処理をしており、一部の panic がサービス全体に影響。
まとめると、「unwrap を使ったからバグが起きた」という単純な話ではなく、むしろ「unwrap によって前提が崩れた時の保証・防御がなかった」ことが本質です。
開発者が気をつけるべき unwrap() の使い方
ここからは、実務的な観点で Rust 開発者として unwrap() をどう扱うべきかを整理します。
✅ 安全に unwrap() を使って良い場面
- プログラムの 初期化段階など、「ここで読み込む値は必ず存在する」という設計が明確な場合。
- テストコードやスクリプト用途で、むしろ panic を発生させて異常を可視化したいとき。
- 性能クリティカルなパスで、「失敗なし/処理速度を優先」という判断をした場面。
ただし、これらの場合でも「なぜ失敗しないのか」をコード・コメント・レビューで明示しておくことが望ましいです。
❌ unwrap() を控えるべき場面
- 本番環境、特に 外部入力/ユーザー入力/ネットワーク/I/O が絡む処理。
- ミッションクリティカルなサービス、分散環境、停止がシステム全体に影響を与える状況。
- 入力仕様が将来変化/拡張可能性がある箇所。
Rust の unwrap() は「安全な前提のもとで選ばれるべき」ものであり、誤用すれば panic によるサービス停止リスクを抱えます。
今回の不具合から学ぶべき設計・運用改善
Cloudflare の事例から、実践的にどのような改善策が考えられるか見ていきます。
🔍 得られる主な教訓
- 前提条件を壊れないものと断定しない
仕様が将来変わる/想定外データが来得ることを前提に設計すべきです。 - 入力/データ構造の拡張・異常値対応を必ず検討する
想定レコード数を超えたケースをどう扱うか事前に策を講じておくこと。 - panic を前提としないサービス設計
Rust でも panic が致命的になり得ます。分散環境では「エラーを局所化」「停止しない仕組み」が重要。 - 監視・アラート・フォールバックの体制整備
panic が起きても即時気づき、フェイルオーバー可能な設計を用意しておくこと。 - レビュー・テスト・コメントによる自己ドキュメント化
unwrap を使った箇所には「なぜこれは失敗しないのか」をレビューで問う習慣を。
✅ 改善すべきコード設計例
// 悪い例(unwrap 使用)
let features = parse_feature_file(path).unwrap();
// 良い例(明示的エラーハンドリング+異常データチェック)
let features = parse_feature_file(path)
.map_err(|e| {
log::error!("feature_file parse failed: {:?}", e);
e
})?;
if features.len() > MAX_FEATURES {
log::warn!("feature file too large: {} entries", features.len());
// フォールバック処理へ
}
さらに、panic の回避設計・監視への連携も視野に入れるべきです。
unwrap() を使うべき/控えるべき基準のチェックリスト
以下に、開発時にチェックできる簡易リストを示します
項目 評価
この値/この処理は 必ず成功する と設計段階で明文化できているか
仕様変更・入力の拡張により前提が壊れる可能性を検討済みか
入力検証・サイズチェック・フォールバックが実装されているか
panic がシステム全体停止につながる構成ではないか
unwrap 使用箇所にコードコメント・レビューが付されているか
うちどれか多く「×」が入るなら、unwrap() ではなくエラー処理を選択すべきと言えます。
まとめ
- Rust の unwrap() は便利な反面、「値が必ずある」と前提することで panic に直結する強力な構文です。
- Cloudflare の障害事例では、Rust 製サービス内での unwrap() が “きっかけ”となり、サービス停止という形で現れましたが、真の原因は「前提の壊れうる性」と「防御設計の欠如」にあります。
- 開発者としては、unwrap() を使うかどうかを設計段階で吟味し、使うならその前提を保証できる構成にしておくこと、使わないなら明示的なエラー処理を実装しておくことが鍵です。
- 本番環境では、特にネットワーク・I/O・分散環境では「失敗しても止まらない/止まっても即復旧できる」設計が欠かせません。
本記事が、Rust を用いた実践的なシステム開発において、unwrap() の使いどころを見極めるための一助となれば幸いです!
Discussion