W# 0.1.1

Arrays and lists

A fixed-length array whose count is in its header, and the growable type built on it.

Arrays

An array is a heap object with its length in the header and its elements inline, which is the same shape a string literal has.

// Arrays, `for` loops, and generic functions over them.
//
// An array is a heap object with its length in the header and its elements
// inline -- the same shape a string literal has. Indexing bounds-checks and
// panics on failure, like `.?` on a null optional: a failure the type system
// permits but the program must not perform.

const array = @import("std/array");
const str = @import("std/str");

// A type parameter is written when it has to be named, and inferred when not.
fn first[T](a: []T) T { return a[0]; }

fn sum(xs: []i64) i64 {
    var total = 0;
    for (xs) |x| { total = total + x; }
    return total;
}

// `for (xs) |x, i|` binds the index as well as the element.
fn show(xs: []i64) str {
    var out = "";
    for (xs) |x, i| {
        if (i > 0) { out = str.concat(out, ", "); }
        out = str.concat(out, str.from_int(x));
    }
    return out;
}

fn main() i64 {
    const xs = []i64{ 3, 1, 4, 1, 5 };
    print(show(xs));
    print_int(sum(xs));
    print_int(first(xs));
    print(first([]str{ "a", "b" }));

    // Every one of these returns a new array: the length lives in the header,
    // so there is no capacity to grow into. `std/list` is the type that has
    // one -- see examples/list.ws.
    print(show(array.push(xs, 9)));
    print(show(array.slice(xs, 1, 4)));
    print(show(array.concat(xs, []i64{ 9, 2 })));

    // `a[i]` is a place as well as a value.
    var ys = []i64{ 1, 2, 3 };
    ys[1] += 10;
    print(show(ys));
    return 0;
}
wsharp run examples/arrays.ws
3, 1, 4, 1, 5
14
3
a
3, 1, 4, 1, 5, 9
1, 4, 1
3, 1, 4, 1, 5, 9, 2
1, 12, 3

Three things to notice.

for binds an index if you ask for one. for (xs) |x| walks the elements and for (xs) |x, i| walks them with their positions.

a[i] is a place as well as a value, so ys[1] += 10 writes back. An index outside the array panics, exactly as .? on a null optional does: a failure the type system permits but the program must not perform.

Every std/array operation returns a new array. The length lives in the header and there is no capacity beside it, so array.push allocates a whole new array every call. That is fine for building something once and wrong for a loop, which is what the next section is about.

An array index is an i64. Every literal index works without saying so, and i64(i) covers the rest.

Lists

list.List[T] is the second object that length needs: a backing array whose header length is the capacity, and a count of how much of it is in use. Pushing writes into the spare tail, only a full list reallocates, and it doubles when it does, so a run of pushes is amortised constant time.

// A growable array.
//
//   wsharp run examples/list.ws
//
// `[]T` is fixed-length: its count lives in the object header, and there is no
// capacity beside it, so `array.push` allocates a whole new array every call.
// `list.List[T]` is the second object that length needs -- a backing array
// whose header length is the *capacity*, and a count of how much of it is in
// use. Pushing writes into the spare tail; only a full list reallocates, and
// it doubles when it does, so a run of pushes is amortised constant time.
const list = @import("std/list");
const str = @import("std/str");

/// Collatz: how many steps `n` takes to reach 1, and the path it took.
fn path(n: i64) list.List[i64] {
    var steps: list.List[i64] = list.new();
    var v = n;
    list.push(steps, v);
    while (v != 1) {
        if (v % 2 == 0) { v = v / 2; } else { v = 3 * v + 1; }
        list.push(steps, v);
    }
    return steps;
}

fn show(xs: list.List[i64]) str {
    var out = "";
    // A list is walked directly: `for` over anything but an array calls `iter`
    // and `next` from the module that declares the type, so `std/list` says
    // how a list is iterated without the type checker knowing it exists.
    for (xs) |v, i| {
        if (i > 0) { out = str.concat(out, " -> "); }
        out = str.concat(out, str.from_int(v));
    }
    return out;
}

fn main() i64 {
    const p = path(7);
    print(show(p));
    print_int(list.len(p));

    // The list grew from nothing, so its capacity is the first power of two
    // past its length -- the spare tail is what makes the next push free.
    print_int(list.capacity(p));

    // Lists of references work the same way; a growth copies them through the
    // write and load barriers, which is why `std/list` is written in W#.
    var words: list.List[str] = list.new();
    list.extend(words, str.split("the quick brown fox", " "));
    list.push(words, "jumps");
    print(str.join(list.to_array(words), ","));

    // `pop` and `remove` hand a value back; both panic rather than returning
    // `?T`, for the reason `a[i]` does.
    print(list.pop(words));
    print(list.remove(words, 0));
    print_int(list.len(words));
    return 0;
}
wsharp run examples/list.ws
7 -> 22 -> 11 -> 34 -> 17 -> 52 -> 26 -> 13 -> 40 -> 20 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2 -> 1
17
32
the,quick,brown,fox,jumps
jumps
the
3

for over a list works, and nothing in the type checker knows what a list is. for over anything that is not an array calls iter and next from the module that declares its type, so std/list says how a list is iterated by writing two functions, and any type you write can do the same.

pop and remove hand a value back and panic on an empty list rather than returning ?T, for the same reason a[i] does: asking for element zero of an empty list is a bug, not a case.

std/list is written in W# rather than in Rust, and the reason is the rule that draws the line for the whole standard library: a builtin may read and write bytes, and anything that moves a reference from one object into another is written in W#, where the write barrier, the load barrier and the stack maps all apply by construction. A list growth copies references, so it is W#.

See the standard library for everything std/list and std/array provide.

Last changed 8 September 2026. Improve this page

On this page