Zig is an open-source programming language designed for robustness, optimality, and clarity.

Download | Documentation | Source Code | Bug Tracker | IRC | Donate $1/month

Feature Highlights

Reading Material

Source Code Examples

Hello World

const std = @import("std");

pub fn main() -> %void {
    // If this program is run without stdout attached, exit with an error.
    var stdout_file = %return std.io.getStdOut();
    // If this program encounters pipe failure when printing to stdout, exit
    // with an error.
    %return stdout_file.write("Hello, world!\n");
}

Build this with:

zig build-exe hello.zig

Hello World with libc

const c = @cImport({
    // See https://github.com/zig-lang/zig/issues/515
    @cDefine("_NO_CRT_STDIO_INLINE", "1");
    @cInclude("stdio.h");
    @cInclude("string.h");
});

const msg = c"Hello, world!\n";

export fn main(argc: c_int, argv: &&u8) -> c_int {
    if (c.printf(msg) != c_int(c.strlen(msg)))
        return -1;

    return 0;
}

Build this with:

zig build-exe hello.zig --library c

Parsing Unsigned Integers

pub fn parseUnsigned(comptime T: type, buf: []u8, radix: u8) -> %T {
    var x: T = 0;

    for (buf) |c| {
        const digit = %return charToDigit(c, radix);
        x = %return mulOverflow(T, x, radix);
        x = %return addOverflow(T, x, digit);
    }

    return x;
}

error InvalidChar;

fn charToDigit(c: u8, radix: u8) -> %u8 {
    const value = switch (c) {
        '0' ... '9' => c - '0',
        'A' ... 'Z' => c - 'A' + 10,
        'a' ... 'z' => c - 'a' + 10,
        else => return error.InvalidChar,
    };

    if (value >= radix)
        return error.InvalidChar;

    return value;
}

error Overflow;

pub fn mulOverflow(comptime T: type, a: T, b: T) -> %T {
    var answer: T = undefined;
    if (@mulWithOverflow(T, a, b, &answer)) error.Overflow else answer
}

pub fn addOverflow(comptime T: type, a: T, b: T) -> %T {
    var answer: T = undefined;
    if (@addWithOverflow(T, a, b, &answer)) error.Overflow else answer
}

fn getNumberWithDefault(s: []u8) -> u32 {
    parseUnsigned(u32, s, 10) %% 42
}

fn getNumberOrCrash(s: []u8) -> u32 {
    %%parseUnsigned(u32, s, 10)
}

fn addTwoTogetherOrReturnErr(a_str: []u8, b_str: []u8) -> %u32 {
    const a = parseUnsigned(u32, a_str, 10) %% |err| return err;
    const b = parseUnsigned(u32, b_str, 10) %% |err| return err;
    return a + b;
}

HashMap with Custom Allocator

const debug = @import("debug.zig");
const assert = debug.assert;
const math = @import("math.zig");
const mem = @import("mem.zig");
const Allocator = mem.Allocator;

const want_modification_safety = !@compileVar("is_release");
const debug_u32 = if (want_modification_safety) u32 else void;

pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn(key: K)->u32,
    comptime eql: fn(a: K, b: K)->bool) -> type
{
    struct {
        entries: []Entry,
        size: usize,
        max_distance_from_start_index: usize,
        allocator: &Allocator,
        // this is used to detect bugs where a hashtable is edited while an iterator is running.
        modification_count: debug_u32,

        const Self = this;

        pub const Entry = struct {
            used: bool,
            distance_from_start_index: usize,
            key: K,
            value: V,
        };

        pub const Iterator = struct {
            hm: &Self,
            // how many items have we returned
            count: usize,
            // iterator through the entry array
            index: usize,
            // used to detect concurrent modification
            initial_modification_count: debug_u32,

            pub fn next(it: &Iterator) -> ?&Entry {
                if (want_modification_safety) {
                    assert(it.initial_modification_count == it.hm.modification_count); // concurrent modification
                }
                if (it.count >= it.hm.size) return null;
                while (it.index < it.hm.entries.len) : (it.index += 1) {
                    const entry = &it.hm.entries[it.index];
                    if (entry.used) {
                        it.index += 1;
                        it.count += 1;
                        return entry;
                    }
                }
                unreachable // no next item
            }
        };

        pub fn init(hm: &Self, allocator: &Allocator) {
            hm.entries = []Entry{};
            hm.allocator = allocator;
            hm.size = 0;
            hm.max_distance_from_start_index = 0;
            // it doesn't actually matter what we set this to since we use wrapping integer arithmetic
            hm.modification_count = undefined;
        }

        pub fn deinit(hm: &Self) {
            hm.allocator.free(Entry, hm.entries);
        }

        pub fn clear(hm: &Self) {
            for (hm.entries) |*entry| {
                entry.used = false;
            }
            hm.size = 0;
            hm.max_distance_from_start_index = 0;
            hm.incrementModificationCount();
        }

        pub fn put(hm: &Self, key: K, value: V) -> %void {
            if (hm.entries.len == 0) {
                %return hm.initCapacity(16);
            }
            hm.incrementModificationCount();

            // if we get too full (60%), double the capacity
            if (hm.size * 5 >= hm.entries.len * 3) {
                const old_entries = hm.entries;
                %return hm.initCapacity(hm.entries.len * 2);
                // dump all of the old elements into the new table
                for (old_entries) |*old_entry| {
                    if (old_entry.used) {
                        hm.internalPut(old_entry.key, old_entry.value);
                    }
                }
                hm.allocator.free(Entry, old_entries);
            }

            hm.internalPut(key, value);
        }

        pub fn get(hm: &Self, key: K) -> ?&Entry {
            return hm.internalGet(key);
        }

        pub fn remove(hm: &Self, key: K) {
            hm.incrementModificationCount();
            const start_index = hm.keyToIndex(key);
            {var roll_over: usize = 0; while (roll_over <= hm.max_distance_from_start_index) : (roll_over += 1) {
                const index = (start_index + roll_over) % hm.entries.len;
                var entry = &hm.entries[index];

                assert(entry.used); // key not found

                if (!eql(entry.key, key)) continue;

                while (roll_over < hm.entries.len) : (roll_over += 1) {
                    const next_index = (start_index + roll_over + 1) % hm.entries.len;
                    const next_entry = &hm.entries[next_index];
                    if (!next_entry.used or next_entry.distance_from_start_index == 0) {
                        entry.used = false;
                        hm.size -= 1;
                        return;
                    }
                    *entry = *next_entry;
                    entry.distance_from_start_index -= 1;
                    entry = next_entry;
                }
                unreachable // shifting everything in the table
            }}
            unreachable // key not found
        }

        pub fn entryIterator(hm: &Self) -> Iterator {
            return Iterator {
                .hm = hm,
                .count = 0,
                .index = 0,
                .initial_modification_count = hm.modification_count,
            };
        }

        fn initCapacity(hm: &Self, capacity: usize) -> %void {
            hm.entries = %return hm.allocator.alloc(Entry, capacity);
            hm.size = 0;
            hm.max_distance_from_start_index = 0;
            for (hm.entries) |*entry| {
                entry.used = false;
            }
        }

        fn incrementModificationCount(hm: &Self) {
            if (want_modification_safety) {
                hm.modification_count +%= 1;
            }
        }

        fn internalPut(hm: &Self, orig_key: K, orig_value: V) {
            var key = orig_key;
            var value = orig_value;
            const start_index = hm.keyToIndex(key);
            var roll_over: usize = 0;
            var distance_from_start_index: usize = 0;
            while (roll_over < hm.entries.len) : ({roll_over += 1; distance_from_start_index += 1}) {
                const index = (start_index + roll_over) % hm.entries.len;
                const entry = &hm.entries[index];

                if (entry.used and !eql(entry.key, key)) {
                    if (entry.distance_from_start_index < distance_from_start_index) {
                        // robin hood to the rescue
                        const tmp = *entry;
                        hm.max_distance_from_start_index = math.max(hm.max_distance_from_start_index,
                            distance_from_start_index);
                        *entry = Entry {
                            .used = true,
                            .distance_from_start_index = distance_from_start_index,
                            .key = key,
                            .value = value,
                        };
                        key = tmp.key;
                        value = tmp.value;
                        distance_from_start_index = tmp.distance_from_start_index;
                    }
                    continue;
                }

                if (!entry.used) {
                    // adding an entry. otherwise overwriting old value with
                    // same key
                    hm.size += 1;
                }

                hm.max_distance_from_start_index = math.max(distance_from_start_index, hm.max_distance_from_start_index);
                *entry = Entry {
                    .used = true,
                    .distance_from_start_index = distance_from_start_index,
                    .key = key,
                    .value = value,
                };
                return;
            }
            unreachable // put into a full map
        }

        fn internalGet(hm: &Self, key: K) -> ?&Entry {
            const start_index = hm.keyToIndex(key);
            {var roll_over: usize = 0; while (roll_over <= hm.max_distance_from_start_index) : (roll_over += 1) {
                const index = (start_index + roll_over) % hm.entries.len;
                const entry = &hm.entries[index];

                if (!entry.used) return null;
                if (eql(entry.key, key)) return entry;
            }}
            return null;
        }

        fn keyToIndex(hm: &Self, key: K) -> usize {
            return usize(hash(key)) % hm.entries.len;
        }
    }
}

test "basic hash map test" {
    var map: HashMap(i32, i32, hash_i32, eql_i32) = undefined;
    map.init(&debug.global_allocator);
    defer map.deinit();

    %%map.put(1, 11);
    %%map.put(2, 22);
    %%map.put(3, 33);
    %%map.put(4, 44);
    %%map.put(5, 55);

    assert((??map.get(2)).value == 22);
    map.remove(2);
    assert(if (const entry ?= map.get(2)) false else true);
}

fn hash_i32(x: i32) -> u32 {
    *(&u32)(&x)
}
fn eql_i32(a: i32, b: i32) -> bool {
    a == b
}

Tetris Clone

Source Code on GitHub

Bare Bones Operating System

Source Code on GitHub

Cat Utility

const std = @import("std");
const io = std.io;
const mem = std.mem;
const os = std.os;

pub fn main() -> %void {
    const exe = os.args.at(0);
    var catted_anything = false;
    var arg_i: usize = 1;
    while (arg_i < os.args.count()) : (arg_i += 1) {
        const arg = os.args.at(arg_i);
        if (mem.eql(u8, arg, "-")) {
            catted_anything = true;
            %return cat_stream(&io.stdin);
        } else if (arg[0] == '-') {
            return usage(exe);
        } else {
            var is = io.InStream.open(arg, null) %% |err| {
                %%io.stderr.printf("Unable to open file: {}\n", @errorName(err));
                return err;
            };
            defer is.close();

            catted_anything = true;
            %return cat_stream(&is);
        }
    }
    if (!catted_anything) {
        %return cat_stream(&io.stdin);
    }
    %return io.stdout.flush();
}

fn usage(exe: []const u8) -> %void {
    %%io.stderr.printf("Usage: {} [FILE]...\n", exe);
    return error.Invalid;
}

fn cat_stream(is: &io.InStream) -> %void {
    var buf: [1024 * 4]u8 = undefined;

    while (true) {
        const bytes_read = is.read(buf[0..]) %% |err| {
            %%io.stderr.printf("Unable to read from stream: {}\n", @errorName(err));
            return err;
        };

        if (bytes_read == 0) {
            break;
        }

        io.stdout.write(buf[0..bytes_read]) %% |err| {
            %%io.stderr.printf("Unable to write to stdout: {}\n", @errorName(err));
            return err;
        };
    }
}

Multiline String Syntax

pub fn createAllShaders() -> AllShaders {
    var as : AllShaders = undefined;

    as.primitive = createShader(
        \\#version 150 core
        \\
        \\in vec3 VertexPosition;
        \\
        \\uniform mat4 MVP;
        \\
        \\void main(void) {
        \\    gl_Position = vec4(VertexPosition, 1.0) * MVP;
        \\}
    ,
        \\#version 150 core
        \\
        \\out vec4 FragColor;
        \\
        \\uniform vec4 Color;
        \\
        \\void main(void) {
        \\    FragColor = Color;
        \\}
    , null);

    as.primitive_attrib_position = as.primitive.attrib_location(c"VertexPosition");
    as.primitive_uniform_mvp = as.primitive.uniform_location(c"MVP");
    as.primitive_uniform_color = as.primitive.uniform_location(c"Color");



    as.texture = createShader(
        \\#version 150 core
        \\
        \\in vec3 VertexPosition;
        \\in vec2 TexCoord;
        \\
        \\out vec2 FragTexCoord;
        \\
        \\uniform mat4 MVP;
        \\
        \\void main(void)
        \\{
        \\    FragTexCoord = TexCoord;
        \\    gl_Position = vec4(VertexPosition, 1.0) * MVP;
        \\}
    ,
        \\#version 150 core
        \\
        \\in vec2 FragTexCoord;
        \\out vec4 FragColor;
        \\
        \\uniform sampler2D Tex;
        \\
        \\void main(void)
        \\{
        \\    FragColor = texture(Tex, FragTexCoord);
        \\}
    , null);

    as.texture_attrib_tex_coord = as.texture.attrib_location(c"TexCoord");
    as.texture_attrib_position = as.texture.attrib_location(c"VertexPosition");
    as.texture_uniform_mvp = as.texture.uniform_location(c"MVP");
    as.texture_uniform_tex = as.texture.uniform_location(c"Tex");

    debug_gl.assert_no_error();

    return as;
}

Mersenne Twister Random Number Generator

const assert = @import("debug.zig").assert;
const rand_test = @import("rand_test.zig");

pub const MT19937_32 = MersenneTwister(
    u32, 624, 397, 31,
    0x9908B0DF,
    11, 0xFFFFFFFF,
    7, 0x9D2C5680,
    15, 0xEFC60000,
    18, 1812433253);

pub const MT19937_64 = MersenneTwister(
    u64, 312, 156, 31,
    0xB5026F5AA96619E9,
    29, 0x5555555555555555,
    17, 0x71D67FFFEDA60000,
    37, 0xFFF7EEE000000000,
    43, 6364136223846793005);

/// Use `init` to initialize this state.
pub const Rand = struct {
    const Rng = if (@sizeOf(usize) >= 8) MT19937_64 else MT19937_32;

    rng: Rng,

    /// Initialize random state with the given seed.
    pub fn init(r: &Rand, seed: usize) {
        r.rng.init(seed);
    }

    /// Get an integer with random bits.
    pub fn scalar(r: &Rand, comptime T: type) -> T {
        if (T == usize) {
            return r.rng.get();
        } else {
            var result: [@sizeOf(T)]u8 = undefined;
            r.fillBytes(result);
            return ([]T)(result)[0];
        }
    }

    /// Fill `buf` with randomness.
    pub fn fillBytes(r: &Rand, buf: []u8) {
        var bytes_left = buf.len;
        while (bytes_left >= @sizeOf(usize)) {
            ([]usize)(buf[buf.len - bytes_left...])[0] = r.rng.get();
            bytes_left -= @sizeOf(usize);
        }
        if (bytes_left > 0) {
            var rand_val_array : [@sizeOf(usize)]u8 = undefined;
            ([]usize)(rand_val_array)[0] = r.rng.get();
            while (bytes_left > 0) {
                buf[buf.len - bytes_left] = rand_val_array[@sizeOf(usize) - bytes_left];
                bytes_left -= 1;
            }
        }
    }

    /// Get a random unsigned integer with even distribution between `start`
    /// inclusive and `end` exclusive.
    // TODO support signed integers and then rename to "range"
    pub fn rangeUnsigned(r: &Rand, comptime T: type, start: T, end: T) -> T {
        const range = end - start;
        const leftover = @maxValue(T) % range;
        const upper_bound = @maxValue(T) - leftover;
        var rand_val_array : [@sizeOf(T)]u8 = undefined;

        while (true) {
            r.fillBytes(rand_val_array);
            const rand_val = ([]T)(rand_val_array)[0];
            if (rand_val < upper_bound) {
                return start + (rand_val % range);
            }
        }
    }

    /// Get a floating point value in the range 0.0..1.0.
    pub fn float(r: &Rand, comptime T: type) -> T {
        // TODO Implement this way instead:
        // const int = @int_type(false, @sizeOf(T) * 8);
        // const mask = ((1 << @float_mantissa_bit_count(T)) - 1);
        // const rand_bits = r.rng.scalar(int) & mask;
        // return @float_compose(T, false, 0, rand_bits) - 1.0
        const int_type = @intType(false, @sizeOf(T) * 8);
        const precision = if (T == f32) {
            16777216
        } else if (T == f64) {
            9007199254740992
        } else {
            @compileError("unknown floating point type")
        };
        return T(r.rangeUnsigned(int_type, 0, precision)) / T(precision);
    }
};

fn MersenneTwister(
    comptime int: type, comptime n: usize, comptime m: usize, comptime r: int,
    comptime a: int,
    comptime u: int, comptime d: int,
    comptime s: int, comptime b: int,
    comptime t: int, comptime c: int,
    comptime l: int, comptime f: int) -> type
{
    struct {
        const Self = this;

        array: [n]int,
        index: usize,

        pub fn init(mt: &Self, seed: int) {
            mt.index = n;

            var prev_value = seed;
            mt.array[0] = prev_value;
            {var i: usize = 1; while (i < n) : (i += 1) {
                prev_value = int(i) +% f *% (prev_value ^ (prev_value >> (int.bit_count - 2)));
                mt.array[i] = prev_value;
            }};
        }

        pub fn get(mt: &Self) -> int {
            const mag01 = []int{0, a};
            const LM: int = (1 << r) - 1;
            const UM = ~LM;

            if (mt.index >= n) {
                var i: usize = 0;

                while (i < n - m) : (i += 1) {
                    const x = (mt.array[i] & UM) | (mt.array[i + 1] & LM);
                    mt.array[i] = mt.array[i + m] ^ (x >> 1) ^ mag01[x & 0x1];
                }

                while (i < n - 1) : (i += 1) {
                    const x = (mt.array[i] & UM) | (mt.array[i + 1] & LM);
                    mt.array[i] = mt.array[i + m - n] ^ (x >> 1) ^ mag01[x & 0x1];

                }
                const x = (mt.array[i] & UM) | (mt.array[0] & LM);
                mt.array[i] = mt.array[m - 1] ^ (x >> 1) ^ mag01[x & 0x1];

                mt.index = 0;
            }

            var x = mt.array[mt.index];
            mt.index += 1;

            x ^= ((x >> u) & d);
            x ^= ((x <<% s) & b);
            x ^= ((x <<% t) & c);
            x ^= (x >> l);

            return x;
        }
    }
}

test "float 32" {
    var r: Rand = undefined;
    r.init(42);

    {var i: usize = 0; while (i < 1000) : (i += 1) {
        const val = r.float(f32);
        assert(val >= 0.0);
        assert(val < 1.0);
    }}
}

test "MT19937_64" {
    var rng: MT19937_64 = undefined;
    rng.init(rand_test.mt64_seed);
    for (rand_test.mt64_data) |value| {
        assert(value == rng.get());
    }
}

test "MT19937_32" {
    var rng: MT19937_32 = undefined;
    rng.init(rand_test.mt32_seed);
    for (rand_test.mt32_data) |value| {
        assert(value == rng.get());
    }
}