最初に
このメモリ管理システムが既存のものとほぼ似ていたらすみません
前回記事:
Aegenはシステムプログラミング向けを想定しているため、GCやARCは実装しません
メモリバグを避けるためにC/C++のように手動で管理はしません
だから、Rustのように所有権システムを導入するといっても、複雑で実装が難しいし、もっと楽に書けるようにしたい
そんな、思いがあり、私は
Guarantee System
というのを考えました(以後、保証システム)
保証システムの概要
保証システムのルールはただ一つだけです(細かいルールはある)
「責任者(変数)」の受けている「保証」が無くなった時に、メモリを解放する
です
ここで、「責任者」と「保証」という言葉が出ました
わかりやすくするために構造体Aを定義しておきます
「責任者」
メモリを解放する責任のある変数のことを指します
let *a = A.new()
作成した構造体を変数aに代入した時、aは構造体Aの責任者となります
責任の移動
let *a2 = a
Aegenでは*を使うことで、責任の移動を示します
このとき、構造体Aの責任者はaからa2になります
値の所有まで移動したわけではないので、引き続き変数aを使うことができます
その他細かいルール
可変の変数の時
以下のコードがあったとします
let mut *a = A.new()
let *a2 = a
a = A.new() // error! aは責任を持っていない
可変変数で値を変更したいときは、代入するとき責任者でなければいけません
なので、以下のようになります
let mut *a = A.new()
let *a2 = a
*a = a2 // 責任移動+代入
a = A.new() // ok
「保証」
「責任者がメモリ解放をここまでは絶対にしない」ということを保証するものです
ある保証が値の存在を保証する期間を保証期間と言います
保証は
- 必ずある「保証」
- 普通の「保証」
- 暗黙の「保証」
の三つがあります
必ずある「保証」
責任者が使われている間を保証期間とする「保証」です
{
let *a = "Hello"
fmt.print(a) // 仮引数が責任者である場合は、*a
// ここでaが解放される
fmt.println(", World!")
}
NLLのようなものだと思ってもらえれば良いかと思います
普通の「保証」
他の変数の「必ずある保証」によって、値の存在を保証するものです
以下のように定義します
let *a = A.new()
// 英語の「〜以上」、「〜以下」を使う
let a2 = A.new() more a // a2はaより「後」にメモリ解放される
let a3 = A.new() less a // a3はaより「前」にメモリ解放される
// コンパイルエラーにならないようにa3定義後、
// aを使わなければいけない
// 一度も使われていない場合は事前に削除される
暗黙の「保証」
主に、同一スコープ内での責任の移動や、共有参照で保証するもので、普通の「保証」を暗黙的にするものです
基本的に、lessを扱います
let *a = A.new()
let a2 = &a // 共有参照
変数a2はaが解放されるまでは使うことができると判断されます
また、責任の移動の時
let *a = A.new()
let *a2 = a
は、変数a2に代入後のaは、共有参照として扱い、a2が解放されるまでは使うことができます
「保証」の大事さ
保証システムにおいて、責任の移動はどちらかというと、「責任者の資格の付与」に近いです。「保証」がなかったら
let mut *a = A.new()
let *a2 = a
a = A.new()
と書け、aとa2が一つの値に対する「責任者」になります。当然、double freeやdangling pointerが発生します
「保証」があるため、「責任者」が常に一つで、変数の使える範囲が決まるのです
個人的に思う保証システムの良いところ
私は保証システムは並行処理に強いと考えます
必ずある「保証」だけだと、Use-After-Freeを避けられません
並行処理に値を渡した後、値が使われていなければ、即解放だからです
Aegenはチャネルを用いるため、そのチャネルで普通の「保証」を適用すれば絶対にUse-After-Freeは起きません(Aegenはどんな場合でも戻り値をチャネルに送信することを要求し、どこかで必ず受信しなければいけないためです)
最後に
この保証システムはまだ実用に至っていないので、メモリバグを防ぐことができるか分かりません
しかし、挑戦する価値は十分にあると思います
最後までこの記事を見てくださりありがとうございます