翻译尚未覆盖全部内容。完整技术参考请查阅英文原文。 查看英文版本 →

Linear Types & Ownership

Resource management through linear types, exclusive references, and move semantics.

线性类型与所有权

Sounio 通过线性类型(linear types)来保障资源安全。线性值必须被恰好使用一次:你不能悄悄丢弃一个文件句柄、数据库连接或 GPU 缓冲区。编译器会拒绝那些忘记消费线性资源的程序。

线性结构体

使用 linear 关键字标记一个结构体,使其要求恰好被消费一次:

linear struct FileHandle {
    fd: i32
}

fn open_file(path: &str) -> FileHandle with IO {
    // OS-level open; returns a linear handle
    FileHandle { fd: sys_open(path) }
}

fn close_file(handle: FileHandle) with IO {
    sys_close(handle.fd)
    // handle is consumed here — compiler satisfied
}

fn main() -> i32 with IO {
    let h = open_file("/tmp/data.txt")
    // If we forget close_file(h), the compiler rejects this program.
    close_file(h)
    return 0
}

如果你移除 close_file(h) 调用,编译器会报错:线性值 h 未被消费。

引用:共享与独占

Sounio 使用 &T 表示共享(只读)引用,&!T 表示独占(可变)引用。这不是 Rust 的 &mut —— &! 符号明确表示独占访问。

fn read_value(r: &i64) -> i64 {
    *r
}

fn increment(r: &!i64) with Mut {
    *r = *r + 1
}

fn main() -> i32 with IO, Mut {
    var x = 10
    println(read_value(&x))   // shared borrow
    increment(&!x)            // exclusive borrow
    println(x)                // prints 11
    return 0
}

编译器会阻止独占引用与指向同一值的任何其他引用同时存在(别名)。

移动语义

非 Copy 类型的值在赋值时会被移动。移动之后,原始绑定不再有效:

fn consume(h: FileHandle) with IO {
    close_file(h)
    return 0
}

fn main() -> i32 with IO {
    let h = open_file("/tmp/data.txt")
    consume(h)
    // h is no longer valid here — using it is a compile error
    return 0
}

仿射放宽

部分类型是仿射(affine)的,而非严格线性的:它们必须被最多使用一次,但可以被静默丢弃。标准标量类型(i32、i64、f64、bool)是 Copy 的,可以自由使用。没有 linear 关键字的自定义结构体遵循移动语义,但不要求必须被消费。

所有权检查的位置

所有权检查器实现在 self-hosted/check/ownership.sio 中,在类型检查之后运行。它跟踪哪些绑定已被移动、哪些正在被借用,并对 linear struct 类型强制执行线性约束。

注意事项

  • 非线性结构体中的嵌套线性字段会被递归跟踪。
  • 当前检查器尚未处理所有涉及 match 分支或闭包中线性值的模式 —— 请查阅功能状态页面了解当前覆盖范围。
  • 对裸数组(&![T; N])进行 &! 修改存在已知限制(CLAUDE.md §13):对于需要对调用方可见的修改,建议使用结构体包装、(*arr)[i] 或按值返回模式。