Walrus/Runtime

Concurrency

Task syntax, containers, views, and the first-pass thread-aware execution model in Walrus.

Updated April 17, 20268 min readWalrus / Concurrency / Tasks / Containers / Views

Walrus now has a thread-aware concurrency surface built around five core pieces:

  • @task
  • spawn
  • await
  • container
  • view<T>

These features are meant to move the language toward a data-oriented, thread-safe, and eventually truly threaded model.

Important First-Pass Status

The current implementation is a first pass.

That means:

  • the language model is thread-aware
  • the data model is being shaped for thread-safe usage
  • the task syntax is already in the language
  • but the runtime does not yet run tasks in parallel on worker threads

Today, spawn still executes through a synchronous first-pass path under the hood and await unwraps the stored result. The syntax, typing rules, and data wrappers are already in place so the scheduler and storage model can evolve later without breaking script code.

So the accurate description is:

  • Walrus is now designed for threaded and thread-safe programming
  • Walrus is not yet running a real parallel scheduler in this first pass

Design Goal

Walrus is moving toward a model where the language stays object-like on the surface, but the runtime becomes increasingly data-oriented underneath.

The intended shape is:

  • containers define structured data that is safe to reason about in concurrent systems
  • views provide lightweight access to batches of container data
  • tasks represent schedulable work units
  • spawn starts work
  • await joins the result

This gives Walrus a concurrency model that is meant to feel simple in scripts while still fitting engine-style execution.


@task

Use @task on a function when that function is intended to be scheduled as asynchronous work.

Walrus
@task
fn buildStuff() -> int:
    return 5

What @task means

  • the function is marked as schedulable work
  • it is expected to be started with spawn
  • it may return a value
  • its result type becomes the payload type of a task handle

What is not allowed

Task functions are not meant to be called like ordinary functions:

Walrus
@task
fn buildStuff() -> int:
    return 5

fn main() -> int:
    let value = buildStuff() # invalid in the current model
    return value

This reports:

TEXT
task functions must be started with spawn

spawn

spawn starts a task function and returns a task handle.

Walrus
@task
fn buildStuff() -> int:
    return 5

fn main() -> int:
    let task = spawn buildStuff()
    let result = await task
    return result

Result type

If a task returns T, then:

  • spawn someTask(...) produces task_handle<T>
  • await on that handle produces T

First-pass runtime behavior

In this first pass, spawn is already the correct language form, but the runtime path is still minimal:

  • the task is executed immediately through the current runtime path
  • the result is wrapped into a task handle
  • no worker-thread pool is active yet

This is deliberate. The language surface is being stabilized before the full scheduler is added.


await

await resolves the result of a task handle.

Walrus
let task = spawn buildStuff()
let result = await task

Type rule

await only accepts task handles.

If you try to await a normal value:

Walrus
let value = 4
let result = await value

you get:

TEXT
await requires a task handle

container

container is the first-pass structured-data declaration for Walrus’ data-oriented direction.

Walrus
container velocity:
    float speed = 5
    vector3 direction

Why container exists

Walrus already has classes and objects, but container is meant for a different role:

  • structured runtime data
  • predictable field layout direction
  • future ECS/storage lowering
  • future concurrency-aware access patterns

In other words, a container is not just “another name for class” conceptually, even though the first-pass implementation reuses the class/type pipeline internally.

First-pass behavior

Right now, containers:

  • define named fields
  • allow optional defaults
  • can be constructed and accessed like other structured runtime values
  • can be used with view<T>

That means this works:

Walrus
container velocity:
    int speed = 5
    int direction_x = 0

fn main() -> int:
    let v = velocity { speed = 2, direction_x = 10 }
    return v.direction_x

view<T>

view<T> is a lightweight access wrapper over container data.

In this first pass, view<T> is backed by:

  • an array of T
  • a start offset
  • a count

It is designed to feel like a non-owning slice-style window over a batch of structured values.

Restriction in this first pass

view<T> currently only accepts container types:

Walrus
fn compute(values: view<velocity>) -> view<velocity>

This is valid if velocity is a container.

This is invalid:

Walrus
fn bad(values: view<int>) -> int:
    return values.len()

because the current rule is:

TEXT
view<T> currently only supports container types

Creating a view

Use viewOf<T>(array) to create a writable view:

Walrus
let world = [
    velocity { speed = 2, direction_x = 10 },
    velocity { speed = 4, direction_x = 20 }
]

let all = viewOf<velocity>(world)

Read-only views

Use readonly() when the view should not permit mutable iteration:

Walrus
let ro = all.readonly()

View APIs

Current first-pass helpers:

MethodDescription
len()Number of items in the view
is_empty()Returns true if no items are present
first()Returns the first item, or nil in empty cases
slice(start, count)Produces a sub-view without copying
readonly()Produces a read-only view wrapper
last()Returns the last item, or nil when empty
take(count)Returns the first count items as a view
skip(count)Returns a view advanced by count items
contains_index(index)Bounds check helper for view-local indices

Example

Walrus
container velocity:
    int speed = 0
    int direction_x = 0

fn main() -> int:
    let world = [
        velocity { speed = 2, direction_x = 10 },
        velocity { speed = 4, direction_x = 20 },
        velocity { speed = 6, direction_x = 30 }
    ]

    let all = viewOf<velocity>(world)
    let middle = all.slice(1, 1)

    if middle.len() == 1:
        return middle.first().direction_x
    return 0

Iterating Views

Views are meant to support cache-friendly batched iteration.

Read-only iteration

Walrus
for v => vel:
    print(v.speed)

Mutable iteration

Walrus
for mut v => vel:
    v.direction_x += v.speed

Readonly safety

Mutable iteration is rejected on read-only views:

Walrus
let ro = viewOf<velocity>(world).readonly()

for mut v => ro:
    v.direction_x += v.speed

This reports:

TEXT
mutable iteration is not allowed on readonly views

Threaded and Thread-Safe Meaning

When we say Walrus is becoming threaded and thread-safe, this is the intended meaning of the model:

  • task functions describe work that can be scheduled independently
  • containers represent structured data meant for disciplined concurrent access
  • views express batch access over container data instead of arbitrary pointer chasing
  • readonly vs writable access is becoming explicit in the language surface

That is the right foundation for:

  • worker-thread scheduling
  • chunked processing
  • ECS-like storage backends
  • task graph execution
  • engine job systems

What is already true

  • the syntax and type system now recognize the concurrency-oriented model
  • task handles and view wrappers already exist in the runtime core
  • the language pushes code toward data batches rather than ad hoc shared mutation

What is not yet true

  • tasks are not yet dispatched across real worker threads
  • the runtime does not yet have a production scheduler
  • there is not yet a full parallel storage backend for containers
  • there is not yet advanced alias analysis or borrow enforcement across task boundaries

So the safest summary is:

Walrus now has a thread-aware, thread-safe-by-design language surface, while real parallel execution is still being built underneath it.


Full Example

Walrus
container velocity:
    int speed = 5
    int direction_x = 0

@task
fn compute(chunk: view<velocity>) -> view<velocity>:
    for mut v => chunk:
        v.direction_x += v.speed
    return chunk

@task
fn buildStuff() -> int:
    return 5

fn main() -> int:
    let world = [
        velocity { speed = 2, direction_x = 10 },
        velocity { speed = 4, direction_x = 20 },
        velocity { speed = 6, direction_x = 30 }
    ]

    let work = viewOf<velocity>(world).slice(0, 2)
    let computeTask = spawn compute(work)
    let setupTask = spawn buildStuff()

    let result = await computeTask
    let setupValue = await setupTask

    print(result.first().direction_x)
    print(setupValue)
    return 0

Deferred Work

The following pieces are intentionally deferred after this first pass:

  • real multi-threaded scheduler execution
  • task stealing or worker pools
  • cancellation
  • futures/promises beyond the basic task handle
  • callback-style for_each(...)
  • native kernel/task integration
  • lower-level storage policies for containers
  • chunk-based parallel iteration APIs