Walrus/Language

Memory Ownership

How Walrus manages memory through ownership, moves, and borrows.

Updated April 17, 20268 min readWalrus / Ownership / Memory / Borrowing

Walrus uses a deterministic ownership model designed for predictable performance in gameplay scripting. Instead of a "one-size-fits-all" garbage collector, Walrus distinguishes between values that are cheap to copy and those that should be moved.


Value Kinds

Every type in Walrus falls into one of two categories: Copy Types or Move Types.

Copy Types (Cheap)

These types are small and passed by value. Assigning them or passing them to a function creates a bitwise copy.

  • int, float, bool
  • enum values
  • fn (Function pointers)
  • shared<T> (Shared pointers)
  • handle<T> (Native handles)
  • ref T and mut ref T (Borrows)
Walrus
let a = 42
let b = a # b is a copy of a
# Both a and b are valid

Move Types (Heavy)

These types are heap-allocated or own resources. To avoid expensive hidden copies, Walrus moves them by default.

  • string
  • Arrays ([T])
  • Objects ({ ... })
  • Class instances
Walrus
let text = "walrus"
let moved = text # ownership moves to 'moved'

# print(text) # ERROR: use of moved value 'text'

Moves and Consuming

A "move" happens when you transfer ownership. Once moved, the source local is considered uninitialized and cannot be used again unless it is re-assigned (if it was declared with var).

Ownership is consumed when:

  • Assigning to another variable.
  • Passing as an argument to a function that expects an owned type.
  • Returning the value from a function.
  • Inserting into a container (Array, Object, or Class).
  • Capturing in an escaping closure.
Walrus
fn take_data(data: [int]):
    # ... data is owned here

let scores = [10, 20]
take_data(scores) # scores moved
# print(scores[0]) # ERROR

Borrowing (ref and mut ref)

Borrowing allows you to grant a function or a block temporary access to a value without moving it.

Read-Only Borrows (ref)

A ref provides immutable access. You can have as many simultaneous read-only borrows as you like.

Walrus
fn print_stats(s: ref string):
    print(s)

let name = "Walrus"
print_stats(name) # Borrowed
print(name)       # Still valid!

Mutable Borrows (mut ref)

A mut ref provides exclusive read-write access.

Walrus
fn clear(data: mut ref [int]):
    # modify data...

let scores = [1, 2, 3]
clear(scores) # Exclusively borrowed

The Rules of Borrowing (v1)

  1. Named Locals Only: Borrows must target a named local variable. You cannot borrow a temporary expression result directly in v1.
  2. Exclusivity: You cannot create a mut ref if any other borrow (read or write) exists for that value.
  3. No Storage: Borrowed references cannot be stored in arrays, objects, or classes. They are strictly lexical.
  4. No Escaping: You cannot return a ref from a function or capture it in an escaping closure.

Closures and Captures

Walrus closures handle captures based on the type:

  1. Copy Types are captured by copy.
  2. Move Types are captured by move. This is why escaping closures are a "consuming" use.
  3. Borrows can only be captured by non-escaping (local) functions. An escaping closure will report an error if it tries to capture a ref.
Walrus
fn make_printer() -> fn():
    let text = "hello"
    fn print_it():
        print(text) # 'text' is MOVED into the closure
    return print_it

Shared Ownership (shared<T>)

When you need multiple parts of your script to truly own the same object (like in a UI graph), use shared<T>.

Walrus
class Node:
    var parent: shared<Node>

let root = shared<Node> { ... }
let child = root # Shared ownership, no move

shared<T> is a copy-type that manages a reference count internally.


Native Handles (handle<T>)

Native handles represent objects owned by the C++ host engine.

  • They are Copy Types.
  • Walrus validates the handle on every access.
  • If the C++ engine deletes the underlying object, Walrus throws a safe RUNTIME_ERROR instead of crashing.

Ownership Checklist

If the value is...Assignment / PassingOriginal Variable
Copy TypeCopyStill Valid
Move TypeMoveInvalid (Moved)
Borrow (ref)Copy (the reference)Still Valid
Shared (shared)Copy (increment refcount)Still Valid
Handle (handle)Copy (the handle)Still Valid