所有权与帽子
Toka 中的所有权完全由帽子原则(Hat Principle)支配。与将内存分配掩盖在泛型类型系统之下的传统做法不同,Toka 使用独特的帽子标记符号(sigil),在语法上将值的所有权模型、拷贝/移动语义以及访问权限昭示得一清二楚。
四种所有权模型
Toka 定义了四种指针类型学,每种对应不同的资源生命周期管理策略:
1. * — 原始指针(人工管理生命周期)
底层原始指针提供零编译期安全保障,必须在 unsafe 块中进行手动分配与释放:
fn raw_ptr_example() {
unsafe {
auto *p# = alloc i32
p = 99
println("raw: {}", p)
}
}
2. ^ — 独占指针(独占堆所有权)
独占指针强制执行严格的独占所有权。在任何时刻,一个堆资源只能有一个 ^ 手柄指向它。当独占指针离开其声明的作用域时,其绑定的内存会被自动、即时地释放:
fn unique_ptr_example() {
auto ^p = new i32(42)
auto ^q = ^p
println("unique: {}", q)
}
3. ~ — 共享指针(引用计数共享所有权)
共享指针允许多个手柄通过线程安全的引用计数共享对同一堆资源的所有权。当最后一个指向它的 ~ 手柄离开作用域时,资源才会被自动释放:
shape Point(x: i32, y: i32, z: i32)
auto ~s1# = new Point(x = 100, y = 200, z = 0)
auto ~s2# = ~s1 // 共享拷贝(引用计数增加)
s2.x = 300 // 直接修改底层灵魂(Soul)的数据
4. & — 借用指针(引用语义)
借用指针是由指针分析层(PAL Checker)静态受检的、对现有灵魂的临时引用,它不持有资源所有权。
移动 vs. 拷贝
Toka 拥有清晰的规则来区分数据的“拷贝”与所有权的“转移”:
默认拷贝语义
默认情况下,Toka 中的普通赋值执行的是拷贝。对于简单标量类型(如 i32、bool)执行按值拷贝,对于复杂的 Shape 结构体执行浅拷贝:
fn move_copy_example() {
auto a# = 42
auto b = a
a = 99
println("a: {}, b: {}", a, b)
}
默认移动语义(独占指针)
移动语义在默认情况下仅适用于独占指针(^)。将一个独占指针赋值给另一个,会自动将堆资源的独占所有权从源转移到目标,这会使源手柄立即失效:
shape Point(x: i32, y: i32, z: i32)
auto ^p1 = new Point(x = 10, y = 20, z = 0)
auto ^p2 = ^p1 // 所有权自动转移(移动)至 p2;p1 之后不再有效!
使用 cede 进行显式移动
对于其他普通类型(如自定义 Shape 或 string),默认执行的是拷贝语义。为了显式转移所有权(从而避免拷贝开销),必须使用 cede 关键字。cede 操作会强制将资源移动到目标,并使原变量立即失效:
auto s1 = string::from("hello")
auto s2 = cede s1 // 显式移动:s1 之后不再有效
函数参数:零拷贝引用捕获
在 Toka 中,函数形参默认是不可变的,并且是通过极其高效的 “零拷贝隐式引用捕获机制” 进行传递的。
编译器会自动在底层以引用形式传递参数,带来零运行时开销和零拷贝。因此,对于标准的函数参数传递,你完全不需要编写任何特殊的指针标记符号,除非你明确打算传递或重绑定指针手柄本身:
fn process(data: i32) {
println("data: {}", data)
}
fn borrow_func_example() {
auto val = 10
process(val)
println("val: {}", val)
}
可变性标记 # 与权限视图(Permission View)
Toka 引入了 # 标记来声明和追踪可变访问权限。它的使用受到编译器生存周期的严苛规范,既消除了冗余的语法噪声,又最大化地提供了视觉安全度。
1. 声明可变性
要在局部变量声明时指定其为可变,必须在声明点的标识符尾部追加 #:
fn mutable_local_example() {
auto val# = 42
val = 99 // Everyday context assignment: NO '#' suffix allowed here!
println("val: {}", val)
}
2. Everyday 上下文(赋值表达式)中严禁标注
一旦变量在声明时被指定为 mutable,其可变状态便已在类型系统的 权限视图(Permission View) 中注册完毕。因此,在日常的赋值表达式或普通使用中,绝对不允许在变量名后追加 # 后缀:
auto val# = 42
val = 99 // Everyday 赋值:禁止写为 val# = 99!否则会引发编译器错误。
这极大地避免了在常规书写中引入无意义的语法噪声。
3. 可变方法调用点(接收者后缀)强制要求
Toka 的一项重要安全设计在于:任何会发生状态修改的方法调用,都必须在调用点进行显式标示。
当调用一个会修改 Shape 状态的方法时,编译器要求在接收者对象(Receiver)后追加 # 后缀(例如 obj#.mutate())。这使得状态的可变性在代码中直观易见,方便代码阅读与后续审计:
shape Counter (
val#: i32 = 0
)
impl Counter@encap {
pub fn clone(self) -> Counter {
return Counter(val = self.val)
}
fn drop(self#) {}
pub fn increment(self#) {
self.val = self.val + 1
}
}
fn mutable_method_example() {
auto c# = Counter(val = 10) // Declaration: '#' is required for mutability
c#.increment() // Mutation Site: '#' receiver suffix is required and highly visible!
println("c.val: {}", c.val)
}
通过将 # 限制在“变量声明点”与“方法接收者可变调用点”,Toka 保持了语法的直观性,并提供了明确的安全可视度。