Build fast, reliable software without giving up control.

  • Readable from the first line to the last
  • Automatic where it helps. Explicit where it matters.
  • Native binaries without a heavyweight runtime
  • Built for tools, applications, and systems work
fun main() {
    println("Hello, Zap!");
}

Predictable memory

Zap’s memory management provides deterministic object lifetimes without stop-the-world pauses.

Explicit error handling

Errors are explicit in the type system, so you can handle failures without exceptions or hidden runtime behavior.

Built to interoperate

Call C libraries through FFI and introduce Zap to an existing codebase one component at a time.

Native performance, without the baggage.

Zap is built for software that needs native performance and direct access to the hardware, without forcing every program to manage memory by hand.

Use the libraries you already rely on and adopt Zap gradually, without rewriting an entire codebase.

Build with Zap

From small command line tools to low-level software, Zap gives you a clear path from idea to native code.

Safe

Safety by default. Control at the boundary.

Zap keeps unsafe operations explicit, so the parts of your program that need extra control stay easy to find and review.

// In C, returning a pointer into a temporary packet would create a dangling
// pointer. Zap tracks which String owns every borrowed StringView instead.

fun receivePacket() String {
    return "event:deploy";
}

fun payload(packet: StringView) StringView borrows(packet) {
    return slice(packet, 6, packet.len - 6);
}

fun printPayload(value: noescape StringView) {
    println("Payload: " + value);
}

fun main() Int {
    // The returned view borrows from a temporary String. Zap keeps that hidden
    // owner alive until the view's last use, so this cannot become use-after-free.
    var temporaryPayload: StringView = payload(receivePacket());
    printPayload(temporaryPayload);

    var packet = "event:first";
    var firstPayload: StringView = payload(packet);
    packet = "event:second";
    printPayload(firstPayload);

    return 0;
}

Error handling, made straightforward.

Keep failure paths clear and local.

Use ?, or, and or err to handle expected failures directly, without exceptions or hidden runtime behavior.

@error
enum CheckoutError {
    EmptyCart,
    NotEnoughStock,
}

fun reserveStock(available: Int, requested: Int) Int!CheckoutError {
    if requested == 0 {
        fail CheckoutError.EmptyCart;
    }
    if requested > available {
        fail CheckoutError.NotEnoughStock;
    }
    return available - requested;
}

fun checkout(available: Int, requested: Int) Int!CheckoutError {
    var remaining = reserveStock(available, requested)?;
    println("Order reserved.");
    return remaining;
}

fun main() Int {
    var remaining = checkout(8, 3) or err {
        if err == CheckoutError.EmptyCart {
            eprintln("Add at least one item before checkout.");
        } else {
            eprintln("The requested quantity is not available.");
        }
        return 1;
    };

    println("Items left in stock: " + toString(remaining));
    return 0;
}

Keep the code you already trust.

Adopt Zap without a rewrite.

Call C libraries through FFI and introduce Zap to an existing codebase one component at a time.

@repr("C")
struct LegacyJob {
    id: Int32,
    priority: Int32
}

ext fun qsort(
    base: *Void,
    count: Int,
    elementSize: Int,
    compare: *fun(*Void, *Void) Int32
) Void;

// qsort calls this Zap function using the C ABI.
@extern("C")
fun compareJobs(left: *Void, right: *Void) Int32 {
    unsafe {
        var a: LegacyJob = *(left as *LegacyJob);
        var b: LegacyJob = *(right as *LegacyJob);

        if a.priority < b.priority { return -1; }
        if a.priority > b.priority { return 1; }
    }
    return 0;
}

fun main() Int {
    var jobs: [3]LegacyJob;
    jobs[0] = LegacyJob { id: 101, priority: 30 };
    jobs[1] = LegacyJob { id: 102, priority: 10 };
    jobs[2] = LegacyJob { id: 103, priority: 20 };

    unsafe {
        var comparator: *fun(*Void, *Void) Int32 = compareJobs;
        qsort(&jobs[0] as *Void, 3, sizeof(LegacyJob), comparator);
    }

    println("Highest priority job: " + toString(jobs[0].id as Int));
    return 0;
}

Make something that lasts.

Open the documentation