メモリ管理

NagiScript のメモリ管理戦略を学びます。


メモリ管理の設計方針

戦略 用途 特徴
スタック確保 基本型、構造体 最速、自動管理
Rc (参照カウント) ヒープ確保 自動解放、安全
Unsafe ポインタ操作 開発者責任
現行仕様(v0.2.0)

NagiScript には借用チェッカーはありません。メモリはスタック確保と参照カウント(Rc)による自動解放が基本です。ポインタ操作は unsafe にまとめ、開発者の責任で扱います。


スタック確保

基本型と小さな構造体はスタックに確保されます:

stack.ngs
val x = 42
val p = Point { x: 1.0, y: 2.0 }

fn add(a: i32, b: i32) -> i32 {
    return a + b
}

Rc (参照カウント)

現行仕様(v0.2.0)

Rc.new(値) / .get() はありますが、.get() の戻り値が意図と異なる場合があります(要検証 / pending)。大きなデータには現行で安定している「構造体 + 値渡し」を推奨します。

大きなオブジェクトや動的サイズのオブジェクトには Rc を使用する設計です:

rc_basic.ngs
val a = Rc.new(42)
val b = a  // 参照カウント: 2
println(b.get())

参照カウントの動作

rc_count.ngs
struct Data { x: i32 }

val a = Rc.new(Data { x: 10 })  // 参照カウント: 1
val b = a                        // 参照カウント: 2
val c = a                        // 参照カウント: 3
// b, c がスコープを抜けると a のみ残り、最終解放時に自動でメモリ解放

リスト (List)

現行仕様(v0.2.0)

List<T> は List.new() / push / len / get / set という API はありますが、LLVM コード生成で zext i64 to i64 になりリンクに失敗する既知バグがあります。当面は使用を避けてください。

動的配列はヒープに確保される設計です。以下は現行ビルドではリンクに失敗するため、参考として記載します。

list.ngs
// 現行はリンク失敗バグがあるため使用不可
var numbers = List.new()
push(numbers, 10)
push(numbers, 20)
println(len(numbers))

推奨: 構造体 + 値渡し

推奨(v0.2.0)

現行で最も安定するのは、List や Rc に頼らず、構造体 + 値渡しでデータを引き回す方法です。構造体はスタックに確保され、コピーも値で渡るため予期しない挙動がありません。

by_value.ngs
struct Data {
    a: i32
    b: i32
}

fn sum(d: Data) -> i32 {
    return d.a + d.b
}

fn main() {
    val d = Data { a: 10, b: 20 }
    println(sum(d))
}

Unsafe ブロックとポインタ

ポインタは *T 型で表し、&x as *T でアドレスを取得します。取り回しは unsafe ブロックにまとめます:

unsafe.ngs
fn read(ptr: *i32) -> i32 {
    unsafe {
        return *ptr
    }
}

fn main() {
    var x = 42
    val p = &x as *i32
    println(read(p))
}
現行仕様(v0.2.0)

ptr-> や ptr.offset(n) は未検証です。ポインタ演算は *ptr と整数演算で代替してください。


C言語とのメモリ共有

C 標準関数は extern "C" fn で宣言して利用できます:

c_memory.ngs
extern "C" fn malloc(size: i32) -> *u8
extern "C" fn free(ptr: *u8)

fn process_c_buffer() {
    val ptr = malloc(100)
    // ptr 経由でメモリを操作(unsafe 経由)
    free(ptr)
}