Ownership & Hats

Ownership in Toka is governed entirely by the Hat Principle. Rather than hiding allocations behind a generic type system, Toka uses distinct hat sigils to make a value's ownership model, copy/move semantics, and access permissions immediately clear directly in the syntax.


Four Ownership Models

Toka defines four pointer typologies, each supporting a distinct resource management strategy:

1. * — Raw Pointer (Manual Lifecycle)

Low-level raw pointers provide zero compile-time safety and must be managed manually within an unsafe block:

fn raw_ptr_example() {
    unsafe {
        auto *p# = alloc i32
        p = 99
        println("raw: {}", p)
    }
}

2. ^ — Unique Pointer (Exclusive Heap Ownership)

Unique pointers enforce strict exclusive ownership. Only one ^ handle can point to a heap resource at a time. When a unique pointer exits its scope, its bound memory is automatically freed:

fn unique_ptr_example() {
    auto ^p = new i32(42)
    auto ^q = ^p
    println("unique: {}", q)
}

3. ~ — Shared Pointer (Reference-Counted Heap Ownership)

Shared pointers allow multiple handles to share ownership of the same heap-allocated resource via thread-safe reference counting. The resource is automatically freed when the last ~ handle goes out of scope:

shape Point(x: i32, y: i32, z: i32)
auto ~s1# = new Point(x = 100, y = 200, z = 0)
auto ~s2# = ~s1  // Shared Copy (ref count increments)
s2.x = 300       // Directly mutates the underlying soul

4. & — Borrow Pointer (Reference Semantics)

Borrow pointers represent temporary, compiler-checked references to existing souls without taking ownership, strictly governed by the Pointer Analysis Layer (PAL).


Moving vs. Copying

Toka has clear rules to distinguish between copying data and transferring ownership:

Default Copy Semantics

By default, standard assignments in Toka perform a copy (value copies for simple scalar types like i32/bool, and shallow copies for standard complex shape types):

fn move_copy_example() {
    auto a# = 42
    auto b = a
    a = 99
    println("a: {}, b: {}", a, b)
}

Default Move Semantics (Unique Pointers)

Move semantics apply by default exclusively to Unique Pointers (^). Assigning one unique pointer to another automatically transfers exclusive ownership from the source to the destination, rendering the source handle immediately invalid:

shape Point(x: i32, y: i32, z: i32)
auto ^p1 = new Point(x = 10, y = 20, z = 0)
auto ^p2 = ^p1  // Ownership transfers (moves) to p2; p1 is no longer valid!

Explicit Move with cede

For other types (such as custom shapes or string), copy semantics are used by default. To explicitly transfer ownership of such a value (avoiding copies), you must use the cede keyword. Ceding transfers the resource to the destination and invalidates the source variable:

auto s1 = string::from("hello")
auto s2 = cede s1 // Explicit move: s1 is invalidated

Function Parameters: Logical In-Place Capture

In Toka, ordinary object parameters use a logical in-place payload capture. This avoids an implicit copy contract, but it is not a hidden lifetime escape: a function that returns a borrow-like value must declare its source dependency, and a task or thread boundary may not capture borrowed state.

Use a hat on a parameter only when the function needs the handle identity itself—for example, to inspect, forward, or rebind it. Use cede in both the parameter declaration and call when ownership must cross a transfer boundary:

fn process(data: i32) {
    println("data: {}", data)
}

fn borrow_func_example() {
    auto val = 10
    process(val)
    println("val: {}", val)
}

The # Mutability Marker & Permission Views

Toka introduces the # marker to explicitly declare and track mutable access. Its usage is governed by strict compiler lifecycles to eliminate redundant code noise while maximizing visual safety.

1. Declaring Mutability

To declare a local variable mutable, you must append # to the identifier at its declaration site:

fn mutable_local_example() {
    auto val# = 42
    val = 99 // Everyday context assignment: NO '#' suffix allowed here!
    println("val: {}", val)
}

2. Forbidden in Everyday Contexts (Assignments)

Once a variable is declared mutable, its mutable state is registered in its Permission View (type system). Therefore, you must NOT append # to the variable in ordinary assignments or expressions:

auto val# = 42
val = 99 // Everyday assignment: NO '#' allowed! Writing 'val# = 99' is a compiler error.

This prevents useless syntactic noise in everyday code.

3. Required at Mutating Method Call Sites (Receiver Suffix)

A key safety feature in Toka is that mutating method calls must be explicitly flagged at the call site.

When invoking a method that mutates a shape, the compiler requires you to append the # suffix to the receiver object (e.g. obj#.mutate()). This makes the state mutation explicit, aiding code readability and auditing:

shape Counter (
    val#: i32 = 0
)


impl Counter {
    pub fn clone(self) -> Counter {
        return Counter(val = self.val)
    }

    pub fn increment(self#) {
        self.val = self.val + 1
    }
}

impl Counter@Encap {
    pub val
    fn drop(self#) {}
}



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)
}

By restricting the # suffix to declarations and mutating method call sites, Toka keeps the syntax intuitive while providing clear safety visibility.