概要
- Memory safety の定義や意味についての議論
- Go言語 のデータレースによるメモリ安全性の問題指摘
- Java など他言語との安全性比較
- Undefined Behavior (未定義動作)の重要性強調
- 言語の 安全保証 の本質的な違いの解説
メモリ安全性とスレッド安全性の区別の曖昧さ
- Memory safety は一般的に「use-after-free」や「out-of-boundsアクセス」を防ぐ言語の特徴とされる
- Thread safety は並行処理におけるバグ(データレース等)を防ぐ性質
- これらを明確に区別することは実際にはあまり有用ではないという主張
- 真に求められる性質は Undefined Behavior(未定義動作) の排除
Go言語におけるデータレースとメモリ安全性
- Goは memory safe と呼ばれることが多いが、データレースによってメモリ安全性が破れる例を紹介
- インターフェース型の値を複数スレッドで同時に操作することで、 不正なメモリアクセスやクラッシュ が発生
- インターフェース値(vtableとデータポインタ)の更新が非アトミックであることが原因
- Goのスライスやマップでも同様の問題が起こり得る
- Goはデータレース検出ツールを標準で提供するが、 型システムや静的保証がないためテスト依存 となる
他言語との比較:Java等
- Java はデータレースがあっても未定義動作には至らないよう設計されている
- 産業用として初の 正式な並行メモリモデル を導入
- 予期しない値(null等)は返るが、不正なポインタアクセスやクラッシュはしない
- 多くの言語(Java, C#, OCaml, JavaScript, WebAssembly)は「全ての並行プログラムが合理的に動作する」ことを重視
- Rust や新しい Swift は、型システムでデータレース自体を排除するアプローチ
Goの安全性の位置付けと問題点
- Goは上記どちらのアプローチも採用していない
- 問題あるデータレースが存在しうるため、厳密には memory safeとは言えない
- データレースがなければメモリアクセスは安全
- Goの公式ドキュメントもこの点を明確に説明していない
- 「ほとんどのレースは限られた結果しか生まない」と説明しつつ、全てではないことを隠している
- JavaやJavaScriptと同等とする主張は誤解を招く
本質的な安全性とは何か
- 本当に重要なのは プログラムが言語仕様を破れないこと
- つまり Undefined Behavior が発生しないこと
- メモリ安全性、スレッド安全性、型安全性などに細分化することにあまり意味はない
- なぜUBが起きたかではなく、「UBが起きるかどうか」が本質
- GoはCよりは安全だが、 保証できる安全性には明確な限界
- 安全性はスペクトラム であり、Goはその中間に位置
総括
- Go は並行プログラミング向けに設計されたが、 データレースによる未定義動作のリスク が存在
- JavaやRust、Swift は安全性確保に大きな努力を払っている
- Goの安全性の限界 を理解し、適切な選択と認識が重要
- 安全性保証 を証明する上で、Goの現状は十分とは言えない
- 言語選択時には、どのような 未定義動作やリスク が残るかを正しく把握する必要