zig

0.6.0 Release Notes

Download & Documentation

Zig is a general-purpose programming language and toolchain for maintaining robust, optimal, and reusable software. With special thanks to many generous sponsors, the Zig project is financially sustainable and currently supports one full-time developer. Let's reboot systems programming.

This release features 6 months of work and changes from 122 different contributors, spread among 2527 commits.

Table of Contents §

LLVM 10 §

This release of Zig upgrades to LLVM 10. Zig operates in lockstep with LLVM; Zig 0.6.0 is not compatible with LLVM 9.

As far as Zig is concerned, the primary benefits of the new LLVM version are bug fixes, especially for ARM Support, MIPS Support, and RISC-V Support.

This is the first release of LLD that has all of Zig's patches merged upstream. Consequently, Zig's source repository no longer includes a fork of LLD sources. Amusingly, it also means that the source tarball zig-0.6.0.tar.xz is 0.5 MiB smaller than zig-0.5.0.tar.xz, since the deletion of LLD sources saved more space than all the rest of the changes made in this release cycle combined. Note that the new Bootstrap Tarball bundles all dependencies of the Zig compiler, which includes LLVM, LLD, and Clang.

Thanks to LemonBoy for submitting patches to update Zig's codebase to LLVM 10, as well as submitting countless bug reports and patches upstream to LLVM and LLD, to get various cross-compiling issues sorted out.

Bootstrap Tarball §

With zig cc now available, the 0.6.0 release of Zig comes with a special new source tarball: zig-bootstrap-0.6.0.tar.xz

This is made from the ziglang/bootstrap source repository, which contains unpatched LLVM, Clang, LLD, and Zig sources, and a simple build script with no branching logic.

The purpose of the bootstrap tarball is to start with minimum system dependencies and end with a fully operational Zig compiler for any target. It does this in exactly 4 steps:

  1. Build LLVM, Clang, and LLD from source, for the native target, using the native C++ compiler.
  2. Build Zig from source for the native target, linking against LLVM, Clang, and LLD.
  3. Now we have Zig as a cross compiler. Use it to rebuild LLVM, Clang, and LLD for the specified target.
  4. Finally, use Zig to build itself, for the specified target.

And thus, the Grand Bootstrapping Plan is fulfilled. The number of steps will always be these four, or less. Never more.

This bootstrap process provides the five new binary builds available in this release, that were not available previously:

See the download page for a full list of tarballs.

Thanks to Timon Kruiper and LemonBoy for contributions related to this.

Support Table §

Zig uses a Tier System to communicate the level of support for different targets. Notably, in this release:

free standing Linux 3.16+ macOS 10.13+ Windows 8.1+ FreeBSD 12.0+ NetBSD 8.0+ UEFI
x86_64 Tier 1 Tier 1 Tier 2 Tier 2 Tier 2 Tier 2 Tier 2
arm64 Tier 1 Tier 2 N/A Tier 3 Tier 3 Tier 3 Tier 3
arm32 Tier 1 Tier 2 N/A Tier 3 Tier 3 Tier 3 Tier 3
mips32 LE Tier 1 Tier 2 N/A N/A Tier 3 Tier 3 N/A
i386 Tier 1 Tier 2 Tier 4 Tier 2 Tier 3 Tier 3 Tier 2
riscv64 Tier 1 Tier 2 N/A N/A Tier 3 Tier 3 Tier 3
bpf Tier 3 Tier 3 N/A N/A Tier 3 Tier 3 N/A
hexagon Tier 3 Tier 3 N/A N/A Tier 3 Tier 3 N/A
mips32 BE Tier 3 Tier 3 N/A N/A Tier 3 Tier 3 N/A
mips64 Tier 3 Tier 3 N/A N/A Tier 3 Tier 3 N/A
amdgcn Tier 3 Tier 3 N/A N/A Tier 3 Tier 3 N/A
sparc Tier 3 Tier 3 N/A N/A Tier 3 Tier 3 N/A
s390x Tier 3 Tier 3 N/A N/A Tier 3 Tier 3 N/A
lanai Tier 3 Tier 3 N/A N/A Tier 3 Tier 3 N/A
powerpc32 Tier 3 Tier 3 Tier 4 N/A Tier 3 Tier 3 N/A
powerpc64 Tier 3 Tier 3 Tier 4 N/A Tier 3 Tier 3 N/A
avr Tier 4 Tier 4 N/A N/A Tier 4 Tier 4 N/A
riscv32 Tier 4 Tier 4 N/A N/A Tier 4 Tier 4 Tier 4
xcore Tier 4 Tier 4 N/A N/A Tier 4 Tier 4 N/A
nvptx Tier 4 Tier 4 N/A N/A Tier 4 Tier 4 N/A
msp430 Tier 4 Tier 4 N/A N/A Tier 4 Tier 4 N/A
r600 Tier 4 Tier 4 N/A N/A Tier 4 Tier 4 N/A
arc Tier 4 Tier 4 N/A N/A Tier 4 Tier 4 N/A
tce Tier 4 Tier 4 N/A N/A Tier 4 Tier 4 N/A
le Tier 4 Tier 4 N/A N/A Tier 4 Tier 4 N/A
amdil Tier 4 Tier 4 N/A N/A Tier 4 Tier 4 N/A
hsail Tier 4 Tier 4 N/A N/A Tier 4 Tier 4 N/A
spir Tier 4 Tier 4 N/A N/A Tier 4 Tier 4 N/A
kalimba Tier 4 Tier 4 N/A N/A Tier 4 Tier 4 N/A
shave Tier 4 Tier 4 N/A N/A Tier 4 Tier 4 N/A
renderscript Tier 4 Tier 4 N/A N/A Tier 4 Tier 4 N/A

WebAssembly Support §

free standing emscripten WASI
wasm32 Tier 2 Tier 3 Tier 2
wasm64 Tier 4 Tier 4 Tier 4

Thanks to Benjamin Feng and Colin Svingen's contributions:

Tier System §

Tier 1 Support §

Tier 2 Support §

Tier 3 Support §

Tier 4 Support §

Windows Support §

Zig's Windows support improved considerably in this release. Counterintuitively, in the Support Table, x86_64-windows went from Tier 1 => Tier 2, but this is due to more SIMD test coverage added, and it was discovered that vectors of f16 are failing some behavior tests. This is the only issue holding Windows (both 32-bit and 64-bit) back from Tier 1.

In this release, the minimum supported Windows version is bumped from 7+ to 8.1+, following the extended support lifecycle of Microsoft.

In addition:

Thanks Jared Miller, emekoi, syscall0, and LemonBoy for contributions related to this.

32-bit Windows Support §

In this release, i386-windows goes from Tier 3 => Tier 2. A pre-made .zip build of 32-bit Windows is newly available.

Thanks to LemonBoy's work on this:

The only thing holding 32-bit Windows back from Tier 1 Support is enabling i386-windows CI builds of Zig that update the download page and the same f16 vector issue from 64-bit Windows.

RISC-V Support §

RISC-V support in Zig is now excellent! We even have riscv64 binary tarballs now thanks to the Bootstrap Tarball. It does, however require one workaround due to clang crashing when it tries to build itself for self-hosted riscv64.

riscv64-freestanding went from Tier 4 => Tier 1.

riscv64-linux went from Tier 4 => Tier 2 and is already nearing Tier 1.

Debug Info and Stack Traces on RISC-V is now working.

The default ABI of riscv32-linux and riscv64-linux is changed to be ilp32d and lp64d, respectively. Likewise, the default ABI of non-linux riscv32 and riscv64 are changed to be ilp32 and lp64. This matches Clang's behavior. (#4863)

Zig now has Test Coverage for riscv64 with no libc and riscv64 with musl libc. The issue for Zig providing glibc for riscv64 is #3340.

Thanks to LemonBoy for contributions related to this, and to Luís Marques for fixing RISC-V issues upstream, which landed in LLVM 10.

ARM Support §

aarch64-linux is very nearly Tier 1. The only thing preventing it is some behavior tests are disabled.

In this release, Zig gained CI Test Coverage for aarch64, and the download page is updated with every master branch commit with a binary tarball for aarch64.

Thanks to the Bootstrap Tarball this release additionally gains a 32-bit ARM binary available (armv7a), as well as another 32-bit slightly older ARM binary (armv6kz) which notably works on Raspberry Pi 1 and RPi 0.

Thank you to Timon Kruiper and LemonBoy for working together to solve undefined behavior bugs revealed by building Zig with zig cc.

32-bit x86 Support §

i386-linux went from Tier 3 => Tier 2, and is nearing Tier 1.

Thanks to the Bootstrap Tarball this release additionally gains a i386-linux binary available.

Thanks LemonBoy for implementing i386 support during this cycle. (#3808, #4408)

MIPS Support §

LemonBoy contributed MIPS fixes:

NetBSD Support §

LemonBoy contributed NetBSD fixes: (#4793)

UEFI Support §

Nick Erdmann and Heppokoyuki contributed UEFI improvements:

macOS Support §

In this release, x86_64-macos went from Tier 1 => Tier 2, however, this is not because Zig dropped any kind of support for macOS, but rather because the bar for meeting Tier 1 requirements was raised, to include "libc is available for this target even when cross-compiling."

Target Details §

Zig's awareness of CPU model and features as well as operating system versions has broadened.

The standard library now has two distinct concepts: std.Target and std.zig.CrossTarget.

CrossTarget is what Zig's command line options get parsed into. It contains the concept of "native" and "default". Once this structure is populated, it can be resolved into a Target.

A Target has all the information available; the CPU, OS, and ABI are all populated. As an example, a CrossTarget might be set to "native", and then when it is resolved, it turns into a Target which has the triple riscv64-linux-musl.

zig build scripts set the desired CrossTarget of a build artifact; the Zig code being compiled only has access to the resolved Target as std.Target.current.

Zig now supports a more fine-grained sense of what is native and what is not. Some examples:

This is now allowed:

-target native

Different OS but native CPU, default Windows C ABI:

-target native-windows

This could be useful for example when running in Wine.

Different CPU but native OS, native C ABI.

-target x86_64-native -mcpu=skylake

Different C ABI but otherwise native target:

-target native-native-musl
-target native-native-gnu

This is a breaking change to std lib APIs for checking the OS and CPU architecture. To update from 0.5.0 to 0.6.0:

builtin.os => builtin.os.tag

builtin.arch => builtin.cpu.arch

std.build.Builder.standardTargetOptions is changed to accept its parameters as a struct with default values. It now has the ability to specify a whitelist of targets allowed, as well as the default target. Rather than two different ways of collecting the target, it's now always a string that is validated, and prints helpful diagnostics for invalid targets. This feature should now be actually useful, and contributions welcome to further improve the user experience.

std.build.LibExeObjStep.setTheTarget is removed. std.build.LibExeObjStep.setTarget is updated to take a CrossTarget parameter.

std.build.LibExeObjStep.setTargetGLibC is removed. glibc versions are handled in the CrossTarget API and can be specified with the -target triple.

std.builtin.Version gains a format method.

Thanks to Timon Kruiper for contributions related to this.

CPU Features §

Zig now has a database of CPU models and CPU features for every architecture. Now that zig targets is self-hosted and outputs JSON, the easiest way to see this is to pipe zig targets into a JSON file and inspect it with a graphical JSON viewer, such as Firefox.

Here I will show you zig targets | jq .native on the laptop that I am typing these release notes on:

{
  "triple": "x86_64-linux.5.4.15...5.4.15-gnu.2.27",
  "cpu": {
    "arch": "x86_64",
    "name": "skylake",
    "features": [
      "64bit",
      "adx",
      "aes",
      "avx",
      "avx2",
      "bmi",
      "bmi2",
      "clflushopt",
      "cmov",
      "cx16",
      "cx8",
      "ermsb",
      "f16c",
      "false_deps_popcnt",
      "fast_gather",
      "fast_scalar_fsqrt",
      "fast_shld_rotate",
      "fast_variable_shuffle",
      "fast_vector_fsqrt",
      "fma",
      "fsgsbase",
      "fxsr",
      "idivq_to_divl",
      "invpcid",
      "lzcnt",
      "macrofusion",
      "merge_to_threeway_branch",
      "mmx",
      "movbe",
      "nopl",
      "pclmul",
      "popcnt",
      "prfchw",
      "rdrnd",
      "rdseed",
      "rtm",
      "sahf",
      "sgx",
      "slow_3ops_lea",
      "sse",
      "sse2",
      "sse3",
      "sse4_1",
      "sse4_2",
      "ssse3",
      "vzeroupper",
      "x87",
      "xsave",
      "xsavec",
      "xsaveopt",
      "xsaves"
    ]
  },
  "os": "linux",
  "abi": "gnu"
}

Here you can see the CPU model and set of CPU features Zig detected. The implementation of this is fully self-hosted. Although Zig properly informs LLVM about CPU features when it does code generation, the awareness of CPU features and detection of CPU features is all implemented in Zig code. Currently, only x86 CPU feature detection is implemented; Zig falls back to LLVM for detecting native CPU model and features on other architectures. Contributions welcome!

Zig now has the ability to parse CPU features as part of the target triple.

Native architecture, OS, and ABI, but baseline CPU features:

-target native -mcpu=baseline

RISC-V 64-bit architecture, OS linux, default ABI, native CPU plus the rdpid feature, minus the sse3 feature:

-target riscv64-linux -mcpu=native+rdpid-sse3

Target the RPi Zero:

-target arm-linux-musleabi -mcpu=arm1176jzf_s

Now that it is possible to select what CPU features are enabled, freestanding no longer has SSE enabled by default.

Thanks to Layne Gustafson for the initial exploration and implementation of this feature, and to alichay for the initial implementation of x86 CPU feature detection.

Thanks to LemonBoy, Michael Dusan, and Noam Preil for related contributions.

Removal of Sub-Architecture §

The whole point of Zig is to re-examine the premises of system programming, and rework abstractions that have shown to be less than ideal. Naturally, once Zig gained CPU feature awareness, it raised the question, what is the purpose of sub-architectures?

As it turns out, the answer is "none". Sub-architectures are rendered redundant by the existence of CPU features, and so they no longer exist in Zig.

This has the happy consequence of making std.Target.Cpu.Arch an enum rather than a tagged union.

Rather than:

-target armv7a-linux-gnu

Now it is:

-target arm-linux-gnu

v7a is considered baseline, so to target a different sub-architecture such as v6kz, it would look like:

-target arm-linux-gnu -mcpu=generic+v6kz

OS Version Ranges §

Operating System version ranges are now part of the target. This means that comptime code has access to exactly which version(s) of an OS are being targeted. You can see this by looking at the output of zig builtin, which displays the source code provided by std.builtin. Here's a snippet of the output on the computer I'm using to type release notes:

// ...
pub const os = Os{
    .tag = .linux,
    .version_range = .{ .linux = .{
        .range = .{
            .min = .{
                .major = 5,
                .minor = 4,
                .patch = 15,
            },
            .max = .{
                .major = 5,
                .minor = 4,
                .patch = 15,
            },
        },
        .glibc = .{
            .major = 2,
            .minor = 27,
            .patch = 0,
        },
    }},
};
// ...

Updated syntax for -target to take into account OS version ranges:

# still valid. default version range
-target x86_64-windows-msvc

# minimum windows version: XP
# maximum windows version: 10
-target x86_64-windows.xp...win10-msvc

# minimum windows version: 7
# maximum windows version: latest
-target x86_64-windows.win7-msvc

# linux example
-target aarch64-linux.3.16...5.3.1-musl

# specifying glibc version
-target mipsel-linux.4.10-gnu.2.1

Here's what it will look like to populate a CrossTarget:

-        tc.target = tests.Target{
-            .Cross = .{
-                .arch = .x86_64,
-                .os = .linux,
-                .abi = .gnu,
-            },
+        tc.target = std.zig.CrossTarget{
+            .cpu_arch = .x86_64,
+            .os_tag = .linux,
+            .abi = .gnu,

Code that used Target.parse need not be updated.

Checking for the OS when doing conditional compilation:

--- a/lib/std/build/run.zig
+++ b/lib/std/build/run.zig
@@ -82,7 +82,7 @@ pub const RunStep = struct {

         var key: []const u8 = undefined;
         var prev_path: ?[]const u8 = undefined;
-        if (builtin.os == .windows) {
+        if (builtin.os.tag == .windows) {
             key = "Path";
             prev_path = env_map.get(key);
             if (prev_path == null) {

std.Target.getStandardDynamicLinkerPath is renamed to std.Target.standardDynamicLinkerPath and no longer requires an allocator.

Zig's method of detecting the native system ABI and dynamic linker is now simple but portable: it inspects the dynamic linker path of its own executable. If statically linked, Zig looks at the dynamic linker path of /usr/bin/env, which is ubiquitous due to its use in shebang lines. Based on the dynamic linker file name, the ABI can be deduced. The same static Zig build will correctly detect the native ABI and dynamic linker path on Debian, NixOS, and Alpine Linux, for example.

No more std.os.foo.is_the_target. It had the downside of running all the comptime blocks and resolving all the usingnamespaces of each system, when just trying to discover if the current system is a particular one. For Darwin, where it's nice to use std.Target.current.isDarwin(), this demonstrates the utility that the proposal #425 would provide.

This change allowed the removal of special Darwin OS version min handling. Now it is integrated with Zig's target OS range. The command line options -mios-version-min and -mmacosx-version-min are removed.

Thanks LemonBoy for contributing OS version detection implementations for Windows and OSX.

Language Changes §

Thanks to Vexu and LemonBoy for contributions related to the above list.

Type Coercion Syntax §

Type coercion (previously called "implicit casting") is now performed with the @as builtin, rather than by calling a type as a function. (#1757)

While a bit more verbose, Zig now has the property that all function calls are always function calls and not type casts, and thus it is no longer required for someone reading Zig code to know the type to determine whether something is a type cast or a function call.

Type coercion is now hooked up into the result location mechanism and additionally now hooked up to variable declarations; this maintains the property that:

var a: T = b;

is semantically equivalent to:

var a = @as(T, b);

Sentinel-Terminated Pointers §

With this change, one feature was added to the language, and one feature was removed.

There are no longer any C string literals such as c"hello". Instead, the type of all string literals is changed from

[]const u8

to

*const [N:0]u8

Where N is the number of bytes in the string literal.

Let's unpack that. Reading the type from left-to-right, they are a reference to: an immutable array of N elements that is followed by an element with value 0, the element type is u8.

Note that the sentinel value is not counted in the length.

This type has the length encoded in multiple ways. This means that it can automatically coerce to both []const u8 (because the length is encoded in the type), and it can also automatically coerce to [*:0]const u8 (because both types are null-terminated and with the help of Slicing with Comptime Indexes).

With this change, Zig string literals now can be passed directly to both C functions which expect null-terminated strings and to Zig functions which accept slices.

sentinel_ptrs.zig

const std = @import("std");

pub fn main() void {
    do_it_the_zig_way("world");
    do_it_the_c_way("world");
}

fn do_it_the_zig_way(arg: []const u8) void {
    std.debug.warn("hello {}\n", .{arg});
}

fn do_it_the_c_way(arg: [*:0]const u8) void {
    _ = std.c.printf("hello %s\n", arg);
}
$ zig build-exe sentinel_ptrs.zig -lc
$ ./sentinel_ptrs
hello world
hello world

Additionally, slicing syntax now has a way to assert that a sentinel exists at a particular element:

slice_sentinel.zig

const std = @import("std");

test "slice with sentinel" {
    var array = [_]i32{ 'a', 'b', 'c', 'd', 'e' };
    const slice = array[1..3 :'d'];
    const result = foo(slice);
    std.testing.expect(result == 'b' + 'c');
}

fn foo(s: [*:'d']i32) i32 {
    var sum: i32 = 0;
    var index: usize = 0;
    while (s[index] != 'd') : (index += 1) {
        sum += s[index];
    }
    return sum;
}
$ zig test slice_sentinel.zig
1/1 test "slice with sentinel"...OK
All 1 tests passed.

If the sentinel is incorrect, a safety check is activated:

test.zig

test "slice with sentinel" {
    var array = [_]i32{ 'a', 'b', 'c', 'd', 'e' };
    const slice = array[1..3 :'f'];
}
$ zig test test.zig
1/1 test "slice with sentinel"...sentinel mismatch
/home/andy/dev/www.ziglang.org/docgen_tmp/test.zig:3:24: 0x204c07 in test "slice with sentinel" (test)
    const slice = array[1..3 :'f'];
                       ^
/home/andy/Downloads/zig/lib/std/special/test_runner.zig:47:28: 0x22bb2e in std.special.main (test)
        } else test_fn.func();
                           ^
/home/andy/Downloads/zig/lib/std/start.zig:253:37: 0x20565d in std.start.posixCallMainAndExit (test)
            const result = root.main() catch |err| {
                                    ^
/home/andy/Downloads/zig/lib/std/start.zig:123:5: 0x20539f in std.start._start (test)
    @call(.{ .modifier = .never_inline }, posixCallMainAndExit, .{});
    ^

Tests failed. Use the following command to reproduce the failure:
/home/andy/dev/www.ziglang.org/docgen_tmp/test

Thanks to LemonBoy, Raul Leal, daurnimator, and Michael Dusan for contributions related to this feature.

Remove Array-to-Reference Type Coercion §

Now that Sentinel-Terminated Pointers is done, the main motivation for type coercion from array values to slices is gone. It's a footgun for Zig to automatically convert a value into a pointer to that value; such an operation should be explicit.

test.zig

test "coerce array value to slice" {
    var array: []const i32 = [_]i32{ 1, 2, 3, 4 };
}
$ zig test test.zig
./docgen_tmp/test.zig:2:36: error: array literal requires address-of operator to coerce to slice type '[]const i32'
    var array: []const i32 = [_]i32{ 1, 2, 3, 4 };
                                   ^
./docgen_tmp/test.zig:2:38: note: referenced here
    var array: []const i32 = [_]i32{ 1, 2, 3, 4 };
                                     ^

How to upgrade code for these new semantics:

coerce_array_ptr.zig

test "coerce array pointer to slice" {
    var array: []const i32 = &[_]i32{ 1, 2, 3, 4 };
}
$ zig test coerce_array_ptr.zig
1/1 test "coerce array pointer to slice"...OK
All 1 tests passed.

This change to simplifies the result location semantics, which helps with reasoning about Zig code, as well as reducing the complexity of a Zig compiler.

Numerical Comparisons §

All numerical comparisons are now allowed no matter the type combinations. For example, small signed integers can be compared against large unsigned integers, and floats can be compared against integers.

For a demonstration of this, you can look at the new std.math.compare function added to the Standard Library and the test cases for it:

compare.zig

const std = @import("std");
const expect = std.testing.expect;

/// See also `Order`.
pub const CompareOperator = enum {
    /// Less than (`<`)
    lt,

    /// Less than or equal (`<=`)
    lte,

    /// Equal (`==`)
    eq,

    /// Greater than or equal (`>=`)
    gte,

    /// Greater than (`>`)
    gt,

    /// Not equal (`!=`)
    neq,
};

/// This function does the same thing as comparison operators, however the
/// operator is a runtime-known enum value. Works on any operands that
/// support comparison operators.
pub fn compare(a: var, op: CompareOperator, b: var) bool {
    return switch (op) {
        .lt => a < b,
        .lte => a <= b,
        .eq => a == b,
        .neq => a != b,
        .gt => a > b,
        .gte => a >= b,
    };
}

test "compare between signed and unsigned" {
    expect(compare(@as(i8, -1), .lt, @as(u8, 255)));
    expect(compare(@as(i8, 2), .gt, @as(u8, 1)));
    expect(!compare(@as(i8, -1), .gte, @as(u8, 255)));
    expect(compare(@as(u8, 255), .gt, @as(i8, -1)));
    expect(!compare(@as(u8, 255), .lte, @as(i8, -1)));
    expect(compare(@as(i8, -1), .lt, @as(u9, 255)));
    expect(!compare(@as(i8, -1), .gte, @as(u9, 255)));
    expect(compare(@as(u9, 255), .gt, @as(i8, -1)));
    expect(!compare(@as(u9, 255), .lte, @as(i8, -1)));
    expect(compare(@as(i9, -1), .lt, @as(u8, 255)));
    expect(!compare(@as(i9, -1), .gte, @as(u8, 255)));
    expect(compare(@as(u8, 255), .gt, @as(i9, -1)));
    expect(!compare(@as(u8, 255), .lte, @as(i9, -1)));
    expect(compare(@as(u8, 1), .lt, @as(u8, 2)));
    expect(@bitCast(u8, @as(i8, -1)) == @as(u8, 255));
    expect(!compare(@as(u8, 255), .eq, @as(i8, -1)));
    expect(compare(@as(u8, 1), .eq, @as(u8, 1)));
}
$ zig test compare.zig
1/1 test "compare between signed and unsigned"...OK
All 1 tests passed.

Thanks to Shawn Landden for the proposal.

Anonymous Struct Literals §

Zig now allows omitting the struct type of a literal. When the result is coerced, the struct literal will directly instantiate the result location, with no copy:

struct_result.zig

const std = @import("std");
const expect = std.testing.expect;

test "anonymous struct literal" {
    checkPoint(.{
        .x = 13,
        .y = 67,
    });
}

fn checkPoint(pt: struct {x: i32, y: i32}) void {
    expect(pt.x == 13);
    expect(pt.y == 67);
}
$ zig test struct_result.zig
1/1 test "anonymous struct literal"...OK
All 1 tests passed.

The struct type can be inferred. Here the result location does not include a type, and so Zig infers the type:

struct_anon.zig

const std = @import("std");
const expect = std.testing.expect;

test "fully anonymous struct" {
    dump(.{
        .int = 1234,
        .float = 12.34,
        .b = true,
        .s = "hi",
    });
}

fn dump(args: var) void {
    expect(args.int == 1234);
    expect(args.float == 12.34);
    expect(args.b);
    expect(args.s[0] == 'h');
    expect(args.s[1] == 'i');
}
$ zig test struct_anon.zig
1/1 test "fully anonymous struct"...OK
All 1 tests passed.

This syntax can also be used to initialize unions without specifying the type:

anon_union.zig

const std = @import("std");
const expect = std.testing.expect;

const Number = union {
    int: i32,
    float: f64,
};

test "anonymous union literal syntax" {
    var i: Number = .{.int = 42};
    var f = makeNumber();
    expect(i.int == 42);
    expect(f.float == 12.34);
}

fn makeNumber() Number {
    return .{.float = 12.34};
}
$ zig test anon_union.zig
1/1 test "anonymous union literal syntax"...OK
All 1 tests passed.

Thanks to Vexu, LemonBoy, dbandstra, and Alexander Naskos for contributing fixes related to this feature.

Tuples Added, Var Args Removed §

Similar to Anonymous Enum Literals and Anonymous Struct Literals, the type can be omitted from array literals. In this example, tuple syntax directly populates the array elements:

tuple.zig

const std = @import("std");
const expect = std.testing.expect;

test "tuple syntax" {
    var array: [4]u8 = .{11, 22, 33, 44};
    expect(array[0] == 11);
    expect(array[1] == 22);
    expect(array[2] == 33);
    expect(array[3] == 44);
}
$ zig test tuple.zig
1/1 test "tuple syntax"...OK
All 1 tests passed.

A tuple is a struct with auto-numbered field names:

infer_tuple.zig

const std = @import("std");
const expect = std.testing.expect;

test "fully anonymous tuple" {
    dump(.{ @as(u32, 1234), @as(f64, 12.34), true, "hi"});
}

fn dump(args: var) void {
    expect(args.@"0" == 1234);
    expect(args.@"1" == 12.34);
    expect(args.@"2");
    expect(args.@"3"[0] == 'h');
    expect(args.@"3"[1] == 'i');
}
$ zig test infer_tuple.zig
1/1 test "fully anonymous tuple"...OK
All 1 tests passed.

However, the @"" syntax is not needed, because although tuples are structs, they also have array-like qualities:

tuples_are_array_like.zig

const std = @import("std");
const expect = std.testing.expect;

test "tuples support element access and .len field" {
    var x: i32 = 1234;
    var y: i32 = 4567;
    var tup = .{ x, y };
    tup[0] += 1; // works as long as the indexes are comptime-known
    tup[1] -= 1;

    expect(tup[0] == 1235);
    expect(tup[1] == 4566);

    // now we iterate over the fields
    var sum: i32 = 0;
    comptime var index = 0;
    inline while (index < tup.len) : (index += 1) {
        sum += tup[index];
    }
    expect(sum == 1235 + 4566);
}

test "tuple concatenation" {
    var one = .{ "hi", true };
    var two = .{ 12.34, .ok };
    var combined = one ++ two;

    expect(combined[3] == .ok);
}
$ zig test tuples_are_array_like.zig
1/2 test "tuples support element access and .len field"...OK
2/2 test "tuple concatenation"...OK
All 2 tests passed.

Zig is determined to remain a small language. With the addition of tuples comes the removal of variadic parameter functions (#208). Printing and formatting are no exception. Formatted printing now uses tuples for the parameters to print, rather than var args:

hello.zig

const std = @import("std");

pub fn main() void {
    std.debug.warn("Hello, {}\n", .{"World!"});
}
$ zig build-exe hello.zig
$ ./hello
Hello, World!

Note: Zig still supports C ABI functions with var args. Nothing is changed there.

Zig's var args design was flawed, with many issues such as var args can't handle void or number literal arguments. With tuples, these issues are resolved. Zig's Tuples are much more robust and generally useful than its var args ever was.

It is planned to add tuple type declaration syntax.

Thanks to Vexu, LemonBoy, dbandstra, and Alexander Naskos for fixes related to this feature.

SIMD §

Zig's SIMD support in 0.6.0 is still far from complete, but significant progress has been made.

Vectors gain element access syntax (#3575, #3580). This introduces the concept of vector index being part of a pointer type. This avoids vectors having well-defined in-memory layout, and allows vectors of any integer bit width to work the same way.

When a vector is indexed with a scalar, this is vector element access, which is implemented in 0.6.0. When a vector is indexed with a vector, this is gather/scatter, which is not available in this release.

vector_elem.zig

const std = @import("std");
const expect = std.testing.expect;

test "vector element access" {
    var v: @Vector(4, i32) = [_]i32{ 1, 5, 3, undefined };

    v[2] = 42;
    expect(v[1] == 5);
    v[3] = -364;
    expect(v[2] == 42);
    expect(-364 == v[3]);

    storev(&v[0], 100);
    expect(v[0] == 100);
}
fn storev(ptr: var, x: i32) void {
    ptr.* = x;
}
$ zig test vector_elem.zig
1/1 test "vector element access"...OK
All 1 tests passed.

Vectors now support comparisons, which returns a vector of bool:

vector_cmp.zig

const std = @import("std");
const expect = std.testing.expect;
const mem = std.mem;

test "vector comparisons" {
    var v: @Vector(4, i32) = [4]i32{ 2147483647, -2, 30, 40 };
    var x: @Vector(4, i32) = [4]i32{ 1, 2147483647, 30, 4 };
    expect(mem.eql(bool, &@as([4]bool, v == x), &[4]bool{ false, false, true, false }));
    expect(mem.eql(bool, &@as([4]bool, v != x), &[4]bool{ true, true, false, true }));
    expect(mem.eql(bool, &@as([4]bool, v < x), &[4]bool{ false, true, false, false }));
    expect(mem.eql(bool, &@as([4]bool, v > x), &[4]bool{ true, false, false, true }));
    expect(mem.eql(bool, &@as([4]bool, v <= x), &[4]bool{ false, true, true, false }));
    expect(mem.eql(bool, &@as([4]bool, v >= x), &[4]bool{ true, false, true, true }));
}
$ zig test vector_cmp.zig
1/1 test "vector comparisons"...OK
All 1 tests passed.

Floating-point vector operations were broken; now they are fixed and no longer require a type parameter (#4027).

Vector division is now supported, including with runtime-safety checks for integer overflow (#4737):

test.zig

const std = @import("std");

test "vector division safety" {
    var a: @Vector(4, i16) = [_]i16{ 1, 2, -32768, 4 };
    var b: @Vector(4, i16) = [_]i16{ 1, 2, -1, 4 };
    const x = div(a, b);
    if (x[2] == 32767) return error.Whatever;
}
fn div(a: @Vector(4, i16), b: @Vector(4, i16)) @Vector(4, i16) {
    return @divTrunc(a, b);
}
$ zig test test.zig
1/1 test "vector division safety"...integer overflow
/home/andy/dev/www.ziglang.org/docgen_tmp/test.zig:10:12: 0x2053be in div (test)
    return @divTrunc(a, b);
           ^
/home/andy/dev/www.ziglang.org/docgen_tmp/test.zig:6:18: 0x204bd6 in test "vector division safety" (test)
    const x = div(a, b);
                 ^
/home/andy/Downloads/zig/lib/std/special/test_runner.zig:47:28: 0x22bc4e in std.special.main (test)
        } else test_fn.func();
                           ^
/home/andy/Downloads/zig/lib/std/start.zig:253:37: 0x2057cd in std.start.posixCallMainAndExit (test)
            const result = root.main() catch |err| {
                                    ^
/home/andy/Downloads/zig/lib/std/start.zig:123:5: 0x20550f in std.start._start (test)
    @call(.{ .modifier = .never_inline }, posixCallMainAndExit, .{});
    ^

Tests failed. Use the following command to reproduce the failure:
/home/andy/dev/www.ziglang.org/docgen_tmp/test

See #903 for more details.

Thanks to Shawn Landden, data-man, and LemonBoy for contributions related to SIMD.

@newStackCall Removed §

The original purpose of @newStackCall was as an exploration for safe recursion, but now the plan for safe recursion is via async functions.

This plus the fact that this builtin had serious flaws, it is now removed from the language.

Whether this builtin will be revived before Zig 1.0 or permanently gone is yet to be determined. To update to Zig 0.6.0, users of this builtin will have to resort to inline assembly.

@call §

@call(options: std.builtin.CallOptions, function: var, args: var) var

This new builtin calls a function, in the same way that invoking an expression with parentheses does, except the parameters are a tuple:

call.zig

const assert = @import("std").debug.assert;

test "noinline function call" {
    assert(@call(.{}, add, .{3, 9}) == 12);
}

fn add(a: i32, b: i32) i32 {
    return a + b;
}
$ zig test call.zig
1/1 test "noinline function call"...OK
All 1 tests passed.

@call allows more flexibility than normal function call syntax does. The CallOptions struct is reproduced here:

pub const CallOptions = struct {
    modifier: Modifier = .auto,

    /// Only valid when `Modifier` is `Modifier.async_kw`.
    stack: ?[]align(std.Target.stack_align) u8 = null,

    pub const Modifier = enum {
        /// Equivalent to function call syntax.
        auto,

        /// Equivalent to async keyword used with function call syntax.
        async_kw,

        /// Prevents tail call optimization. This guarantees that the return
        /// address will point to the callsite, as opposed to the callsite's
        /// callsite. If the call is otherwise required to be tail-called
        /// or inlined, a compile error is emitted instead.
        never_tail,

        /// Guarantees that the call will not be inlined. If the call is
        /// otherwise required to be inlined, a compile error is emitted instead.
        never_inline,

        /// Asserts that the function call will not suspend. This allows a
        /// non-async function to call an async function.
        no_async,

        /// Guarantees that the call will be generated with tail call optimization.
        /// If this is not possible, a compile error is emitted instead.
        always_tail,

        /// Guarantees that the call will inlined at the callsite.
        /// If this is not possible, a compile error is emitted instead.
        always_inline,

        /// Evaluates the call at compile-time. If the call cannot be completed at
        /// compile-time, a compile error is emitted instead.
        compile_time,
    };
};

The builtins @noInlineCall and @inlineCall are removed; instead @call supports .modifier = .never_inline, and .modifier = .always_inline.

Additionally, the .never_tail and .always_tail modifiers are available (#3732). These are still experimental; proper compile errors are not implemented to detect when these modifiers are used incorrectly.

For an explanation of .no_async, see noasync.

Thanks to LemonBoy for contributions related to this feature.

callconv §

Old syntax for a function that has the C calling convention:

extern fn foo() void {}

New syntax:

fn foo() callconv(.C) void {}

In Zig 0.6.0, zig fmt automatically transforms the old syntax to the new syntax. In Zig 0.7.0, it will no longer do that.

Similarly the keywords stdcallcc and nakedcc are obsoleted by callconv(.Stdcall) and callconv(.Naked).

The enum that callconv takes as a parameter is defined in std.builtin.CallingConvention:

pub const CallingConvention = enum {
    Unspecified,
    C,
    Cold,
    Naked,
    Async,
    Interrupt,
    Signal,
    Stdcall,
    Fastcall,
    Vectorcall,
    Thiscall,
    APCS,
    AAPCS,
    AAPCSVFP,
};

This allows the calling convention of a function to depend on comptime logic, which can be useful for dealing with code that works differently on different architectures.

Thanks to LemonBoy for implementing this.

Non-Exhaustive Enums §

A Non-exhaustive enum can be created by adding a trailing '_' field. It must specify an integer tag type and may not consume every enumeration value.

@intToEnum on a non-exhaustive enum never fails.

A switch on a non-exhaustive enum can include a '_' prong as an alternative to an else prong with the difference being that it makes it a compile error if all the known tag names are not handled by the switch.

switch_non_exhaustive_enum.zig

const std = @import("std");
const assert = std.debug.assert;

const Number = enum(u8) {
    One,
    Two,
    Three,
    _,
};

test "switch on non-exhaustive enum" {
    const number = Number.One;
    const result = switch (number) {
        .One => true,
        .Two,
        .Three => false,
        _ => false,
    };
    assert(result);
    const is_one = switch (number) {
        .One => true,
        else => false,
    };
    assert(is_one);
}
$ zig test switch_non_exhaustive_enum.zig
1/1 test "switch on non-exhaustive enum"...OK
All 1 tests passed.

Non-exhaustive enums are useful for future-proofing code, so that it will continue to work correctly even when encountering values that were not present at the time the code was written.

Various bits in the Standard Library have been updated to use non-exhaustive enums rather than numerical constants.

Thanks to Vexu, LemonBoy, and daurnimator for contributions related to this feature.

Unicode Character Literals §

unicode_char_lit.zig

const std = @import("std");

test "utf8 character literal" {
    const x = '💩';
    std.testing.expect(x == 128169);
}
$ zig test unicode_char_lit.zig
1/1 test "utf8 character literal"...OK
All 1 tests passed.

This makes sense because Zig is defined to have UTF-8 Source Encoding. A unicode character literal is a comptime_int with the value equal to the code point.

Thanks to Nick Erdmann for implementing this feature.

Atomics §

Thanks to Vexu:

Container-Level Doc Comments §

//! This is a container doc comment, which applies to the
//! entire file rather than the `foo` declaration below.

/// This is a declaration doc comment, which applies to
/// the `foo` declaration below.
const foo = bar;

Thanks Marc Tiehuis for the proposal (#2288) and Vexu for the implementation (#3697).

Comptime Struct Fields §

comptime_struct_field.zig

const std = @import("std");

const Foo = struct {
    a: i32,
    comptime b: i32 = 1234,
};

test "example" {
    var foo: Foo = undefined;
    comptime std.debug.assert(foo.b == 1234);
}
$ zig test comptime_struct_field.zig
1/1 test "example"...OK
All 1 tests passed.

A comptime struct field requires a default initialization value. Loads from a comptime struct field result in a comptime value of the default initialization value. Stores to a comptime struct field assert that the stored value is the default initialization value.

Generally, one should use a global const instead of a comptime field. The reason for using a comptime field is when you want reflection over struct fields to find the data as a field. For example:

csf_example.zig

const std = @import("std");

fn dump(args: var) void {
    inline for (std.meta.fields(@TypeOf(args))) |field| {
        std.debug.warn("{} = {}\n", .{field.name, @field(args, field.name)});
    }
}

pub fn main() void {
    var runtime_float: f32 = 12.34;
    dump(.{
        .int = 1234,
        .float = runtime_float,
        .b = true,
        .s = "hi",
        .T = [*]f32,
    });
}
$ zig build-exe csf_example.zig
$ ./csf_example
int = 1234
float = 1.23400001e+01
b = true
s = hi
T = type

This will construct an anonymous struct with all comptime fields (except float) and pass it to dump. Each iteration in the for loop will evaluate the @field(...) expression and produce a comptime value, except float, which will be a runtime value.

This feature makes formatted printing, and tuples in general, support mixed comptime and runtime values (#3677).

Untyped Struct Fields §

It's now possible to omit the type from struct fields. This allows the field to have any value of any type. The catch is that it causes the entire struct to be required to be comptime-known.

untyped_struct_fields.zig

const std = @import("std");
const expect = std.testing.expect;

test "struct with var field" {
    const Point = struct {
        x: var,
        y: var,
    };
    comptime var pt = Point {
        .x = 1,
        .y = 2,
    };
    expect(pt.x == 1);
    expect(pt.y == 2);

    pt.x = true;
    pt.y = "hello";
    expect(pt.x);
    expect(std.mem.eql(u8, pt.y, "hello"));
}
$ zig test untyped_struct_fields.zig
1/1 test "struct with var field"...OK
All 1 tests passed.

The motivation behind this feature is to expose default struct field initialization values and sentinel values in @typeInfo:

pub const StructField = struct {
    name: []const u8,
    offset: ?comptime_int,
    field_type: type,
    default_value: var,
};

With Zig 0.6.0, this works now:

type_info_struct.zig

const std = @import("std");
const expect = std.testing.expect;

test "access default initialization value" {
    const Foo = struct {
        x: i32 = 1234,
        y: i32,
    };
    const info = @typeInfo(Foo).Struct;
    expect(info.fields[0].default_value.? == 1234);
    expect(info.fields[1].default_value == null);
}
$ zig test type_info_struct.zig
1/1 test "access default initialization value"...OK
All 1 tests passed.

Similarly, the @typeInfo for Sentinel-Terminated Pointers now exposes the sentinel value.

It is planned to rename var to anytype in this context, to disambiguate it from variable declarations.

Thanks to LemonBoy for contributions related to this feature.

Pointer Arithmetic and Alignment §

Pointer arithmetic now appropriately modifies the alignment of a pointer type:

ptr_arith_align.zig

const std = @import("std");
const expect = std.testing.expect;

test "pointer math alignment" {
    var arr: [10]u8 align(4) = undefined;
    var runtime_known_2: usize = 2;

    const ptr: [*]u8 = &arr;
    const ptr2 = ptr + 1;
    const ptr3 = ptr + 2;
    const ptr4 = ptr + runtime_known_2;

    comptime {
        expect(@TypeOf(ptr) == [*]align(4) u8);
        expect(@TypeOf(ptr2) == [*]u8);
        expect(@TypeOf(ptr3) == [*]align(2) u8);
        expect(@TypeOf(ptr4) == [*]u8);
    }
}
$ zig test ptr_arith_align.zig
1/1 test "pointer math alignment"...OK
All 1 tests passed.

Thanks to LemonBoy for implementing this (#1528).

@export §

@export(target: var, comptime options: std.builtin.ExportOptions) void

@export now uses std.builtin.ExportOptions to accept its parameters:

pub const ExportOptions = struct {
    name: []const u8,
    linkage: GlobalLinkage = .Strong,
    section: ?[]const u8 = null,
};

The section option is new; it is now possible to specify the linksection using @export.

Thanks LemonBoy for implementing this (#2679).

@bitSizeOf §

@bitSizeOf(comptime T: type) comptime_int

This function returns the number of bits it takes to store T in memory. The result is a target-specific compile time constant.

This function measures the size allocated at runtime. For types that are disallowed at runtime, such as comptime_int and type, the result is 0.

Note that this value does not necessarily equal @sizeOf(T) * 8. For example, @bitSizeOf(u7) is 7, but @sizeOf(u7) is 1.

When the accepted proposal for align(0) fields is implemented, @bitSizeOf measures how many bits a type would take up in a struct if all fields were align(0).

Thanks to Vexu for the implementation of this.

No More Capture Aliasing §

Captured payloads from optionals and tagged-unions are no longer aliases to the same memory of the optional or tagged-union. The (unwrapped) payloads are copies.

no_capture_aliasing.zig

const std = @import("std");
const expect = std.testing.expect;

test "no capture value aliasing" {
    // In Zig 0.5.0, foo() returns 5678.
    expect(foo() == 1234);
}

fn foo() i32 {
    var optional_x: ?i32 = 1234;

    if (optional_x) |x| {
        optional_x = 5678;
        return x;
    }

    unreachable;
}
$ zig test no_capture_aliasing.zig
1/1 test "no capture value aliasing"...OK
All 1 tests passed.

There are two competing proposals for non-copyable data structures: #3803 #3804

When one of these is accepted, it will be a compile error to copy some types. To avoid copying, one can denote the capture value to make it a pointer:

capture_aliasing.zig

const std = @import("std");
const expect = std.testing.expect;

test "capture value aliasing" {
    expect(foo() == 5678);
}

fn foo() i32 {
    var optional_x: ?i32 = 1234;

    if (optional_x) |*x| {
        optional_x = 5678;
        return x.*;
    }

    unreachable;
}
$ zig test capture_aliasing.zig
1/1 test "capture value aliasing"...OK
All 1 tests passed.

Thanks to LemonBoy for implementing this.

noasync §

noasync, similar to comptime, creates a scope in which the programmer asserts there will be no suspension points.

Normally, async function calls and awaiting an async function frame introduce a suspension point at the callsite, causing the containing function to have the async calling convention. However, inside a noasync scope, async function calls and awaiting async function frames do not cause a suspension point. Instead, the code asserts that the callee never suspends, or in the case of await, that the function frame already has the result completed.

This allows a non-async function to call an async function:

noasync.zig

const std = @import("std");
const expect = std.testing.expect;

test "noasync function call" {
    const result = noasync add(50, 100);
    expect(result == 150);
}

fn add(a: i32, b: i32) i32 {
    if (a > 100) {
        suspend;
    }
    return a + b;
}
$ zig test noasync.zig
1/1 test "noasync function call"...OK
All 1 tests passed.

This is especially useful for main() to set up async functions initially:

async_main.zig

const std = @import("std");
const expect = std.testing.expect;

var global_frame_1: anyframe = undefined;
var global_frame_2: anyframe = undefined;

pub fn main() void {
    var main_frame = async asyncMain();
    resume global_frame_1;
    resume global_frame_2;
    const result = noasync await main_frame;
    std.debug.warn("result: {}\n", .{result});
}

fn asyncMain() i32 {
    var a = async foo();
    var b = async bar();
    return await a + await b;
}

fn foo() i32 {
    global_frame_1 = @frame();
    suspend;
    return 1;
}

fn bar() i32 {
    global_frame_2 = @frame();
    suspend;
    return 2;
}
$ zig build-exe async_main.zig
$ ./async_main
result: 3

Notice that the function asyncMain is able to participate in the async/await abstraction without having to care about the setup and teardown happening in main.

For Async I/O in the Standard Library, Zig handles this setup and teardown in the Start Code that calls main.

Now, watch what happens when we remove noasync from the above example:

oops_await.zig

const std = @import("std");
const expect = std.testing.expect;

var global_frame_1: anyframe = undefined;
var global_frame_2: anyframe = undefined;

pub fn main() void {
    var main_frame = async asyncMain();
    resume global_frame_1;
    resume global_frame_2;
    const result = await main_frame;
    std.debug.warn("result: {}\n", .{result});
}

fn asyncMain() i32 {
    var a = async foo();
    var b = async bar();
    return await a + await b;
}

fn foo() i32 {
    global_frame_1 = @frame();
    suspend;
    return 1;
}

fn bar() i32 {
    global_frame_2 = @frame();
    suspend;
    return 2;
}
$ zig build-exe oops_await.zig
/home/andy/Downloads/zig/lib/std/start.zig:83:1: error: function with calling convention 'Naked' cannot be async
fn _start() callconv(.Naked) noreturn {
^
/home/andy/Downloads/zig/lib/std/start.zig:123:5: note: async function call here
    @call(.{ .modifier = .never_inline }, posixCallMainAndExit, .{});
    ^
/home/andy/Downloads/zig/lib/std/start.zig:179:17: note: async function call here
    std.os.exit(@call(.{ .modifier = .always_inline }, callMainWithArgs, .{ argc, argv, envp }));
                ^
/home/andy/Downloads/zig/lib/std/start.zig:188:36: note: async function call here
    return initEventLoopAndCallMain();
                                   ^
/home/andy/Downloads/zig/lib/std/start.zig:225:12: note: async function call here
    return @call(.{ .modifier = .always_inline }, callMain, .{});
           ^
/home/andy/Downloads/zig/lib/std/start.zig:243:22: note: async function call here
            root.main();
                     ^
./docgen_tmp/oops_await.zig:11:20: note: await here is a suspend point
    const result = await main_frame;
                   ^
./docgen_tmp/oops_await.zig:18:12: note: await here is a suspend point
    return await a + await b;
           ^
./docgen_tmp/oops_await.zig:22:22: note: @frame() causes function to be async
    global_frame_1 = @frame();
                     ^

Here, the await inside main is a suspension point, which causes main to have the async calling convention, which has a cascading effect, causing _start to have the async calling convention. But _start already has the "naked" calling convention, because it is the entry point from the kernel!

We can use noasync to create a "seam" between async code and blocking code, because in this example, we know that main_frame has already completed by the time we call await.

Thanks to Vexu for contributions related to this feature.

Deprecated Builtins Removed §

Many deprecated builtins have been removed.

Thanks to Maciej Walczak for removing these and implementing the corresponding std lib functions:

Thanks to Vexu for removing these:

Allow Empty Inferred Error Sets §

The body of functions returning inferred error sets are no longer required to return any possible errors.

empty_inferred_error_set.zig

fn foo() !void {}

test "" {
    foo() catch |err| switch (err) {};
}
$ zig test empty_inferred_error_set.zig
1/1 test ""...OK
All 1 tests passed.

Thanks to LemonBoy for implementing this.

@TypeOf Supports Multiple Parameters §

Multiple parameters can now be specified with @TypeOf in cases where Peer Type Resolution is needed.

// std.math.max
pub fn max(x: var, y: var) @TypeOf(x, y) {
    return if (x > y) x else y;
}

Thanks to Josh Wolfe for proposal and LemonBoy for implementing this.

Underscore Separators in Number Literals §

Underscores may be placed between two digits as a visual separator. Consecutive underscores are not allowed.

fn digits() void {
    _ = 1_234_567;
    _ = 0xff00_00ff;
    _ = 0b10000000_10101010;
    _ = 0b1000_0000_1010_1010;
    _ = 0x123_190.109_038_018p102;
    _ = 3.14159_26535_89793;
}

Thanks to Marc Tiehuis for original proposal and momumi for making a strong case to re-open the proposal, and for implementing it.

Slicing with Comptime Indexes §

When slicing where the length is comptime-known, the expression type is now a single-item pointer to array *[N]T . Prior to this change an error-prone @ptrCast was required.

slice_comptime_indexes.zig

const std = @import("std");
const assert = std.debug.assert;

test "slicing with comptime indexes" {
    var a = "abcdefgh".*;
    assert(@TypeOf(a) == [8:0]u8);

    // both indices are comptime, thus length is comptime
    var b = a[3..6];
    assert(@TypeOf(b) == *[3]u8);

    // length is runtime
    var runtime_i: usize = 3;
    var c = a[runtime_i..6];
    assert(@TypeOf(c) == []u8);

    // copy array
    a[0..3].* = a[5..8].*;
    assert(std.mem.eql(u8, &a, "fghdefgh"));
}
$ zig test slice_comptime_indexes.zig
1/1 test "slicing with comptime indexes"...OK
All 1 tests passed.

Thanks to Jimmi Holst Christensen for proposing this.

errdefer Payload §

errdefer now provides syntax to access the in-flight error.

errdefer_payload.zig

const std = @import("std");

fn perform() !void {
    errdefer |err| std.debug.assert(err == error.Overflow);
    _ = try std.math.add(u8, 255, 1);
}

test "errdefer with payload" {
    perform() catch return;
    unreachable;
}
$ zig test errdefer_payload.zig
1/1 test "errdefer with payload"...OK
All 1 tests passed.

Thanks to Byron Heads for the proposal and LemonBoy for implementing this.

Follow-up proposal: errdefer with unreachable should allow function type to not have an error union

Standard Library §

There are so many breaking changes that it is not feasible to list them all here. Instead, the release notes will cover contributions and high-level topics. In the future, it should be possible to use the same backend of Documentation Generation to make a tool that detects all API changes - additions, removals, and modifications.

Async I/O §

Async I/O in 0.6.0 is still experimental, but rapidly approaching usable.

kprotty contributed significant improvements to synchronization primitives. kprotty writes:

std.Mutex uses a simple locking scheme for Linux, relies on CriticalSection for Windows and falls back to spinlocking on other platforms. There are two parts towards improving it:

1) Adaptive Locking

For high contention cases, eager blocking mutexes incur a penalty of a syscall when they may not need to. In order to address this, the mutex can spin for a little bit trying to acquire the lock similar to a spinlock before deciding to block. This improves performance when the time spent in the critical section is minimal and acquiring/releasing is done frequently. The implementation chosen for this was that of lock_futex.go from Golang 1.13 as it provides a nice balance between spinning and deciding to block (another possibility could be rust/webkit parking_lot). Because this implementation only needs a futex interface, it can be reused:

2) Parker API

Most synchronization primitives such as Mutexes, RwLocks, Condvars, Events and Semaphores can be built upon atomic instructions and futexes for handling blocking. Another point of this change was to setup a cross-platform futex (Parker) interface in which other primitives as listed above could be built off of. The default one provided is ThreadParker which differs from the current blocking implementation scheme:

Results & Future Implications

Because the Parker now has a standardized interface, one could replace ThreadParker with something like AsyncParker and reuse the synchronization primitive code for std.event synchronization objects. In order to demonstrate this, kprotty provided some example code for AsyncParker as well as a naive benchmark to test the performance of std.Mutex in comparison to this new adaptive mutex: zig-adaptive-lock

The results for high contended, small critical section cases are promising:

These synchronization primitives are building blocks for an event loop, which is what drives async I/O.

In 0.6.0, you can start to see the ideas behind the standard library's event loop coming together. Notably, Start Code will set up an event loop before calling main(), when the root source file defines:

pub const io_mode = .evented;

This means that main() is now allowed to use await. Same with tests, and zig test supports --test-evented-io which affects whether the test runner sets io_mode to evented or blocking.

The standard library's async I/O integration, together with I/O Streams improvements, are now capable of passing behavior tests with --test-evented-io enabled. Standard library tests are now compiling successfully with evented I/O mode, but the event loop implementation needs to be improved in order for tests to pass. Additionally, "glue" code is needed to be added to the event loop implementation to support more operating systems. It's not quite stable enough to be added to CI Test Coverage.

In previous versions of Zig, there was a std.event namespace for APIs that only applied to evented I/O, but in this release, many of these APIs have been removed, superceded by normal APIs integrating properly into async I/O. For example, std.event.fs is removed and all the normal std.fs (Filesystem) APIs work correctly for both blocking and evented I/O modes.

Here is an example of a simple program that writes to a file:

write_file_blocking.zig

const std = @import("std");

pub fn main() anyerror!void {
    const file = try std.fs.cwd().createFile("hello.txt", .{});
    defer file.close();

    try file.writeAll("hello\n");
}
$ zig build-exe write_file_blocking.zig
$ ./write_file_blocking

Looking at the strace, we can see it is quite simple:

arch_prctl(ARCH_SET_FS, 0x233190)       = 0
rt_sigaction(SIGSEGV, {sa_handler=0x22a8d0, sa_mask=[], sa_flags=SA_RESTORER|SA_RESTART|SA_RESETHAND|SA_SIGINFO, sa_restorer=0x204310}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
rt_sigaction(SIGILL, {sa_handler=0x22a8d0, sa_mask=[], sa_flags=SA_RESTORER|SA_RESTART|SA_RESETHAND|SA_SIGINFO, sa_restorer=0x204310}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
rt_sigaction(SIGBUS, {sa_handler=0x22a8d0, sa_mask=[], sa_flags=SA_RESTORER|SA_RESTART|SA_RESETHAND|SA_SIGINFO, sa_restorer=0x204310}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
openat(AT_FDCWD, "hello.txt", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 3
write(3, "hello\n", 6)                  = 6
close(3)                                = 0
exit_group(0)                           = ?

Set up thread-local storage, attach some signal handlers for debugging, openat, write, close, done. Now we enable evented I/O:

write_file_evented.zig

const std = @import("std");

pub const io_mode = .evented;

pub fn main() anyerror!void {
    const file = try std.fs.cwd().createFile("hello.txt", .{});
    defer file.close();

    try file.writeAll("hello\n");
}
$ zig build-exe write_file_evented.zig
$ ./write_file_evented

I can't paste the full strace output here, because it is too long, but I'll highlight some of the interesting parts:

clone(child_stack=0x7fe36dbdeff8, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID|0x400000strace: Process 8891 attached
, parent_tid=[8891], tls=0x7fe36dbdf028, child_tidptr=0x7fe36dbdf000) = 8891
futex(0x7fe36dbdf000, FUTEX_WAIT, 8891, NULL <unfinished ...>
[pid  8891] futex(0x260b50, FUTEX_WAIT, 0, NULL <unfinished ...>
[pid  8891] <... futex resumed>)        = 0
[pid  8891] openat(AT_FDCWD, "hello.txt", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 20
[pid  8891] epoll_ctl(18, EPOLL_CTL_ADD, 17, {EPOLLIN|EPOLLOUT|EPOLLONESHOT|EPOLLET, {u32=1841168864, u64=140614775472608}}) = 0
[pid  8891] futex(0x260b50, FUTEX_WAIT, 0, NULL <unfinished ...>
[pid  8891] <... futex resumed>)        = 0
[pid  8891] write(20, "hello\n", 6)     = 6
[pid  8891] epoll_ctl(18, EPOLL_CTL_MOD, 17, {EPOLLIN|EPOLLOUT|EPOLLONESHOT|EPOLLET, {u32=1841168864, u64=140614775472608}}) = 0
[pid  8891] futex(0x260b50, FUTEX_WAIT, 0, NULL <unfinished ...>
[pid  8891] <... futex resumed>)        = -1 EAGAIN (Resource temporarily unavailable)
[pid  8891] close(20 <unfinished ...>
[pid  8891] <... close resumed>)        = 0
[pid  8891] exit(0 <unfinished ...>
[pid  8891] <... exit resumed>)         = ?
<... futex resumed>)                    = -1 EAGAIN (Resource temporarily unavailable)
exit_group(0)                           = ?

Here we can see that a separate thread is created, which ends up doing the file system I/O. Some operating systems such as Linux do not have async file system support, and so the technique used by evented I/O libraries is to have a thread pool for doing blocking operations. In this way, you can make anything async by giving the task to another thread.

Now that Linux has io_uring, this could be improved. With Zig's OS Version Ranges, the event loop code could be improved to detect if io_uring is within the target OS version range, and take advantage of it if so. If the minimum OS version is high enough, the non-io_uring code could be omitted, and if the maximum OS version is low enough, the io_uring code could be omitted. If the OS version range includes both, then the code should try io_uring, and fall back at runtime to a non-io_uring strategy.

Anyway, the point here is that because evented I/O is enabled, it now becomes meaningful to express concurrency:

concurrent.zig

const std = @import("std");

pub const io_mode = .evented;

pub fn main() anyerror!void {
    var a_frame = async doA();
    var b_frame = async doB();

    try await a_frame;
    try await b_frame;
}

fn doA() !void {
    const file = try std.fs.cwd().createFile("a.txt", .{});
    defer file.close();

    try file.writeAll("A\n");
}

fn doB() !void {
    const file = try std.fs.cwd().createFile("b.txt", .{});
    defer file.close();

    try file.writeAll("B\n");
}
$ zig build-exe concurrent.zig
$ ./concurrent

I'll refrain from pasting more strace output here, but now we can start to see things happening in parallel (depending on the OS support for async file system I/O, or the file system thread pool size).

Finally, I want to point out one crucial point about Zig's async I/O. It still works if you switch back to blocking I/O:

async_blocking.zig

const std = @import("std");

pub fn main() anyerror!void {
    var a_frame = async doA();
    var b_frame = async doB();

    try await a_frame;
    try await b_frame;
}

fn doA() !void {
    const file = try std.fs.cwd().createFile("a.txt", .{});
    defer file.close();

    try file.writeAll("A\n");
}

fn doB() !void {
    const file = try std.fs.cwd().createFile("b.txt", .{});
    defer file.close();

    try file.writeAll("B\n");
}
$ zig build-exe async_blocking.zig --release-fast
$ ./async_blocking

This time I will show the strace since it's very short:

arch_prctl(ARCH_SET_FS, 0x203cf0)       = 0
openat(AT_FDCWD, "a.txt", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 3
write(3, "A\n", 2)                      = 2
close(3)                                = 0
openat(AT_FDCWD, "b.txt", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 3
write(3, "B\n", 2)                      = 2
close(3)                                = 0
exit_group(0)                           = ?

You can see that the async stuff folded into simple, linear, blocking code.

This is a big deal. It means that Zig code can express concurrency, yet be reusable in both a blocking I/O and an evented I/O environment. There is no "async-std". The Zig Standard Library supports both async and blocking I/O with the same codebase.

Thanks to Benjamin Feng, Vexu, daurnimator, and Timon Kruiper for contributions related to this feature.

Debug Info and Stack Traces §

LemonBoy made a number of improvements to Zig's debug info code:

Additionally, Rocknest brought Windows segfault handler code on par with POSIX. (#4319)

Formatted Printing §

Formatted printing is now documented fairly well thanks to Felix Queißner (#3474).

std.fmt.format is modified for the new I/O Streams API, and because of Tuples Added, Var Args Removed. It now operates cleanly with Async I/O.

Formatting capabilities were improved:

An API overhaul is still planned.

Thanks to daurnimator, LemonBoy, Benjamin Feng, Felix Queißner, Michael Dusan, Nathan Michaels, data-man, frmdstryr, markfirmware, shiimizu, and vegecode for contributions related to this feature.

I/O Streams §

The bad news: there were breaking changes to I/O streams and you have to update your code.

The good news:

Example code using the old streams API (lifted from my advent-of-code repository):

const std = @import("std");

pub fn main() anyerror!void {
    var stdin_unbuf = std.io.getStdIn().inStream();
    // damn that is pretty painful, isn't it?
    const in = &std.io.BufferedInStream(@TypeOf(stdin_unbuf).Error).init(&stdin_unbuf.stream).stream;

    var sum: u64 = 0;
    var line_buf: [50]u8 = undefined;
    while (try in.readUntilDelimiterOrEof(&line_buf, '\n')) |line| {
        if (line.len == 0) break;
        const module_mass = try std.fmt.parseInt(u64, line, 10);
        const fuel_required = (module_mass / 3) - 2;
        sum += fuel_required;
    }

    const out = &std.io.getStdOut().outStream().stream;
    try out.print("{}\n", .{sum});
}

New streams API:

const std = @import("std");

pub fn main() anyerror!void {
    const in = std.io.bufferedInStream(std.io.getStdIn().inStream()).inStream();

    var sum: u64 = 0;
    var line_buf: [50]u8 = undefined;
    while (try in.readUntilDelimiterOrEof(&line_buf, '\n')) |line| {
        if (line.len == 0) break;
        const module_mass = try std.fmt.parseInt(u64, line, 10);
        const fuel_required = (module_mass / 3) - 2;
        sum += fuel_required;
    }

    const out = std.io.getStdOut().outStream();
    try out.print("{}\n", .{sum});
}

And unlike before, it works if you utilize Async I/O with:

pub const io_mode = .evented;

InStream, OutStream, and SeekableStream were already generic across error sets; it's not really worse to make them generic across the vtable as well, which is what this change did.

See #764 for the open issue acknowledging that using generics for these abstractions is a design flaw.

See #130 for the efforts to make these abstractions non-generic.

This also changes the OutStream API so that write returns number of bytes written, and writeAll is the one that loops until the whole buffer is written.

Filesystem §

The file system APIs are starting to come together. I think this is an area where Zig really shines.

There were many breaking changes during this release cycle, however they are all working directly towards a clear vision:

These goals go hand-in-hand. By avoiding string manipulation of paths, Zig code simultaneously avoids the need for allocators, prevents error.NameTooLong errors from the kernel, and avoids TOCTOU bugs.

Here is an example from zig build of what it looks like to "install" all the files from a source directory into a destination directory:

var src_dir = try std.fs.cwd().openDir(build_output_dir, .{ .iterate = true });
defer src_dir.close();

var dest_dir = try std.fs.cwd().openDir(output_dir, .{});
defer dest_dir.close();

var it = src_dir.iterate();
while (try it.next()) |entry| {
    _ = try src_dir.updateFile(entry.name, dest_dir, entry.name, .{});
}

No allocator needed. Resource management is trivial. Works correctly when the file paths are so deeply nested that they would be longer than PATH_MAX. Avoids copying the file when the destination is already up-to-date. When a file copy does happen, it uses sendfile if supported, so that the file copy happens in the kernel. The ignored return value there is whether or not the destination file was found to be out-of-date, so that the code could know which files were fresh and which were stale.

When upgrading your filesystem code to Zig 0.6.0, you should ask yourself the question, can I rework the logic to avoid path manipulation?

I won't list all the functions added, removed, and changed here because it is too many, but I will highlight some notable changes:

Thank you to contributors LeRoyce Pearson, Jonathan S, daurnimator, LemonBoy, Terin Stock, dimenus, and stratact.

Networking §

The basics of networking are starting to come together, at least on POSIX.

Added std.net.getAddressList - basic DNS address resolution.

Added std.net.StreamServer.

Added std.net.tcpConnectToHost.

Added std.net.tcpConnectToAddress.

Added std.net.Address.parseIp which supports IPv4 and IPv6.

Added std.net.Address.parseExpectingFamily which additionally accepts a family parameter.

std.os IPPROTO constants are canonicalized.

Example of a simple TCP chat server using Async I/O.

Thank you to contributors Luna, Vexu, Jonathan Marler, Sebastian, frmdstryr, and LemonBoy.

JSON §

Here is an example of parsing into an arbitrary struct:

json_parse_struct.zig

const std = @import("std");
const json = std.json;

test "parse into struct with misc fields" {
    @setEvalBranchQuota(10000);
    const options = json.ParseOptions{ .allocator = std.testing.allocator };
    const T = struct {
        int: i64,
        float: f64,
        @"with\\escape": bool,
        @"withąunicode😂": bool,
        language: []const u8,
        optional: ?bool,
        default_field: i32 = 42,
        static_array: [3]f64,
        dynamic_array: []f64,

        const Bar = struct {
            nested: []const u8,
        };
        complex: Bar,

        const Baz = struct {
            foo: []const u8,
        };
        veryComplex: []Baz,

        const Union = union(enum) {
            x: u8,
            float: f64,
            string: []const u8,
        };
        a_union: Union,
    };
    const r = try json.parse(T, &json.TokenStream.init(
        \\{
        \\  "int": 420,
        \\  "float": 3.14,
        \\  "with\\escape": true,
        \\  "with\u0105unicode\ud83d\ude02": false,
        \\  "language": "zig",
        \\  "optional": null,
        \\  "static_array": [66.6, 420.420, 69.69],
        \\  "dynamic_array": [66.6, 420.420, 69.69],
        \\  "complex": {
        \\    "nested": "zig"
        \\  },
        \\  "veryComplex": [
        \\    {
        \\      "foo": "zig"
        \\    }, {
        \\      "foo": "rocks"
        \\    }
        \\  ],
        \\  "a_union": 100000
        \\}
    ), options);
    defer json.parseFree(T, r, options);
    std.testing.expectEqual(@as(i64, 420), r.int);
    std.testing.expectEqual(@as(f64, 3.14), r.float);
    std.testing.expectEqual(true, r.@"with\\escape");
    std.testing.expectEqual(false, r.@"withąunicode😂");
    std.testing.expectEqualSlices(u8, "zig", r.language);
    std.testing.expectEqual(@as(?bool, null), r.optional);
    std.testing.expectEqual(@as(i32, 42), r.default_field);
    std.testing.expectEqual(@as(f64, 66.6), r.static_array[0]);
    std.testing.expectEqual(@as(f64, 420.420), r.static_array[1]);
    std.testing.expectEqual(@as(f64, 69.69), r.static_array[2]);
    std.testing.expectEqual(@as(usize, 3), r.dynamic_array.len);
    std.testing.expectEqual(@as(f64, 66.6), r.dynamic_array[0]);
    std.testing.expectEqual(@as(f64, 420.420), r.dynamic_array[1]);
    std.testing.expectEqual(@as(f64, 69.69), r.dynamic_array[2]);
    std.testing.expectEqualSlices(u8, r.complex.nested, "zig");
    std.testing.expectEqualSlices(u8, "zig", r.veryComplex[0].foo);
    std.testing.expectEqualSlices(u8, "rocks", r.veryComplex[1].foo);
    std.testing.expectEqual(T.Union{ .float = 100000 }, r.a_union);
}
$ zig test json_parse_struct.zig
1/1 test "parse into struct with misc fields"...OK
All 1 tests passed.

Thank you daurnimator, Sebastian Keller, xackus, hryx, and Lachlan Easton for related contributions.

Bring-Your-Own-OS Abstraction Layer §

In the previous release of Zig (0.5.0), the std.Target.Os enum recognized the following operating systems:

  freestanding
  ananas
  cloudabi
  dragonfly
  freebsd
  fuchsia
  ios
  kfreebsd
  linux
  lv2
  macosx
  netbsd
  openbsd
  solaris
  windows
  haiku
  minix
  rtems
  nacl
  cnk
  aix
  cuda
  nvcl
  amdhsa
  ps4
  elfiamcu
  tvos
  watchos
  mesa3d
  contiki
  amdpal
  hermit
  hurd
  wasi
  emscripten
  zen
  uefi

This list was the list of targets that LLVM supported + zen (a hobby OS not maintained for over 1 year) + UEFI.

It doesn't make sense to put every hobby OS into this list, but it does make sense to support them! It should be possible for people to take advantage of Zig's cross platform abstractions without having to get support for their hobby OS upstreamed into Zig.

Zig 0.6.0 does two things:

This allows hobby OS developers to maintain a zig package that makes the Zig Standard Library support their OS. Application developers could use it like this:

pub const os = @import("my_hobby_os_package");

pub fn main() void {
    // ...
}

Next, standard library abstractions will detect when they should utilize this. If the operating system is POSIX compliant, then many things will Just Work. For example, std.os.read was defined like this:

/// Returns the number of bytes that were read, which can be less than
/// buf.len. If 0 bytes were read, that means EOF.
/// If the application has a global event loop enabled, EAGAIN is handled
/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
    if (builtin.os == .windows) {
        return windows.ReadFile(fd, buf);
    }

    if (builtin.os == .wasi and !builtin.link_libc) {
        const iovs = [1]iovec{iovec{
            .iov_base = buf.ptr,
            .iov_len = buf.len,
        }};

        var nread: usize = undefined;
        switch (wasi.fd_read(fd, &iovs, iovs.len, &nread)) {
            0 => return nread,
            else => |err| return unexpectedErrno(err),
        }
    }

    while (true) {
        const rc = system.read(fd, buf.ptr, buf.len);
        switch (errno(rc)) {
            0 => return @intCast(usize, rc),
            EINTR => continue,
            EINVAL => unreachable,
            EFAULT => unreachable,
            EAGAIN => if (std.event.Loop.instance) |loop| {
                loop.waitUntilFdReadable(fd);
                continue;
            } else {
                return error.WouldBlock;
            },
            EBADF => unreachable, // Always a race condition.
            EIO => return error.InputOutput,
            EISDIR => return error.IsDir,
            ENOBUFS => return error.SystemResources,
            ENOMEM => return error.SystemResources,
            ECONNRESET => return error.ConnectionResetByPeer,
            else => |err| return unexpectedErrno(err),
        }
    }
    return index;
}

Where system referred to:

/// When linking libc, this is the C API. Otherwise, it is the OS-specific system interface.
pub const system = if (builtin.link_libc) std.c else switch (builtin.os) {
    .macosx, .ios, .watchos, .tvos => darwin,
    .freebsd => freebsd,
    .linux => linux,
    .netbsd => netbsd,
    .dragonfly => dragonfly,
    .wasi => wasi,
    .windows => windows,
    .zen => zen,
    else => struct {},
};

Now, else => struct{} is modified to look for @import("root").os if it is provided. With this modification, as long as the OS package defines all the constants (such as fd_t and EISDIR), then std.os.write would end up calling the write function from the hobby OS package, and everything Just Works.

Some abstractions do not work so smoothly; in this case there is code that looks something like this:

fn doTheOsThing() void {
    if (@hasDecl(root, "os") and @hasDecl(root.os, "doTheOsThing")) {
        return @import("root").os.doTheOsThing();
    }
}

Note there is not even a check for "other" here. Allowing applications to override fundamental OS functions is useful on any operating system.

Now that this is implemented, Zig has first class support for all operating systems. The main difference between upstream-recognized OSes and "other" OSes would be where the support is maintained: in zig's std lib, or in a third party "OS layer" package.

With this new feature, upstream support for Zen hobby OS is removed. This has the additional benefit of clearing up some confusion, since there is already a Zen programming language.

This feature is still experimental, and contributions are welcome if you need to tweak the std lib to get it working for your hobby OS use case.

Thanks Christine Dodrill and Noam Preil for related contributions.

Follow-up proposal: BYO os should work at the zig level

ArrayList §

Apologies - when I first created the ArrayList API, I got it backwards. I made items the slice of allocated memory, and you had to call a function to get the slice of valid objects.

Now, the items field is safe to use directly, and is always the slice of valid objects, and the capacity is maintained separately.

This breaks callsites of ArrayList but it removes a footgun from this API. It also allows removing a bunch of no-longer-needed API.

Iterator API is removed from std.ArrayList, since it is now possible to use a for loop on the items field.

Additionally:

Thanks daurnimator, xackus, Bas, MCRusher, and Benoit Giannangeli for related contributions.

Special thanks to daurnimator for making the case to rename std.Buffer to std.ArrayListSentineled, and replace usages of that API with usages to ArrayList where applicable.

Memory §

Thanks to Bas van den Berg, Emeka Nkurumeh, Jonathan Marler, Michaël Larouche, Sebastian, Timon Kruiper, daurnimator, and xackus for related contributions.

Crypto §

lukechampine added an AES implementation to std.crypto. data-man improved the code, replacing variables with constants. lukechampine additionally added support for AES-CTR.

daurnimator added a Gimli based PRNG to std.rand, added gimli to the crypto hash benchmark, and added AEAD modes for Gimli. (#4369)

Jay Petacat added a BLAKE3 hashing algorithm (#4366). Jay writes:

This is a translation of the official reference implementation with few other changes. The bad news is that the reference implementation is designed for simplicity and not speed, so there's a lot of room for performance improvement. The good news is that, according to the crypto benchmark, the implementation is still fast relative to the other hashing algorithms:

             md5: 430 MiB/s
            sha1: 386 MiB/s
          sha256: 191 MiB/s
          sha512: 275 MiB/s
        sha3-256: 233 MiB/s
        sha3-512: 137 MiB/s
         blake2s: 464 MiB/s
         blake2b: 526 MiB/s
          blake3: 576 MiB/s
        poly1305: 1479 MiB/s
        hmac-md5: 653 MiB/s
       hmac-sha1: 553 MiB/s
     hmac-sha256: 222 MiB/s
          x25519: 8685 exchanges/s

J.W fixed index out of bounds logic in some hashing algorithms.

Start Code §

Logic involving startup code has been moved from being hard-coded in the compiler to comptime logic inside the start.zig file from the standard library.

Additionally, the startup code is un-special-cased.

Previously, the compiler had special logic to determine whether to include the startup code, which was in std/special/start.zig. Now, the file is moved to std/start.zig, and there is no special logic in the compiler. Instead, the standard library unconditionally imports the start.zig file, which then has a comptime block that does the logic of determining what, if any, start symbols to export. Instead of start.zig being in its own special package, it is just another normal file that is part of the standard library.

std.builtin.TestFn is now part of the standard library rather than specially generated by the compiler.

Additionally, some minor changes to Thread-Local Storage handling (#4807):

Thanks to LemonBoy, Vexu, Jared Miller, and Nick Erdmann for contributions related to this.

Documentation §

Thanks to Vexu, xackus, LemonBoy, data-man, Benjamin Feng, Emilio G. Cota, Jonathan Marler, MateuszOkulus, Matt Keeter, Maximilian Hunt, Nathan Michaels, Nick Erdmann, Robin Voetter, Shritesh, hryx, momumi, and yvt for contributions to the language reference.

Documentation Generation §

This feature is still experimental.

There is a new -fdump-analysis command line option, which creates a $NAME-analysis.json file with all of the finished semantic analysis that the stage1 compiler produced. It contains types, packages, declarations, and files.

This feature can be used to power IDE integration features until such time as the self-hosted compiler is available and supports such features more directly.

Additionally, there is a proof-of-concept documentation generation feature (#21):

The new -femit-docs CLI option outputs:

In this strategy, we have 1 static html page and 1 static javascript file, which loads the semantic analysis dump directly and renders it using DOM manipulation.

There is now experimental std lib documentation.

There are still some missing features. For example, it does not handle generic types ideally, multiple packages are not handled well, and some URLs are broken. Additionally, the merge_anal_dumps tool is not yet complete, so generated documentation can only apply to a single build configuration. For example, if the generated docs targeted Windows, then Linux-only functions will not be shown in the documentation, and vice-versa. Due to Zig's lazy analysis many declarations are not semantically analyzed, causing them to be omitted in the generated documentation. These are all open issues to be addressed.

Despite the flaws it can still be a useful way to explore the Standard Library. It has motivated some contributions to improve doc comments to various APIs:

Thank you Rocknest, Timon Kruiper, Henry Wu, Felix Queißner, Vexu, dtw-waleee, pfg, and xackus for related contributions.

Safety §

Related to Async I/O, resuming non-suspended functions now has runtime safety (#3469):

bad_resume.zig

const std = @import("std");

fn foo() void {
    var f = async bar(@frame());
    std.process.exit(0);
}

fn bar(frame: anyframe) void {
    suspend {
        resume frame;
    }
    std.process.exit(0);
}

pub fn main() void {
    _ = async foo();
}
$ zig build-exe bad_resume.zig
$ ./bad_resume
resumed a non-suspended function
/home/andy/dev/www.ziglang.org/docgen_tmp/bad_resume.zig:3:1: 0x22ea7c in foo (bad_resume)
fn foo() void {
^
/home/andy/dev/www.ziglang.org/docgen_tmp/bad_resume.zig:10:9: 0x230419 in bar (bad_resume)
        resume frame;
        ^
/home/andy/dev/www.ziglang.org/docgen_tmp/bad_resume.zig:4:13: 0x22ea4f in foo (bad_resume)
    var f = async bar(@frame());
            ^
/home/andy/dev/www.ziglang.org/docgen_tmp/bad_resume.zig:16:9: 0x22a875 in main (bad_resume)
    _ = async foo();
        ^
/home/andy/Downloads/zig/lib/std/start.zig:243:22: 0x20476f in std.start.posixCallMainAndExit (bad_resume)
            root.main();
                     ^
/home/andy/Downloads/zig/lib/std/start.zig:123:5: 0x20454f in std.start._start (bad_resume)
    @call(.{ .modifier = .never_inline }, posixCallMainAndExit, .{});
    ^
(process terminated by signal)

Additionally the following safety checks have been added:

Thanks LemonBoy, Alexandros Naskos, and xackus for related contributions.

Much more safety is planned. This was a relatively quiet release cycle as far as safety is concerned, despite the ambitions of the 0.5.0 roadmap.

zig build §

zig build is still in an experimental, proof-of-concept phase, and will remain that way until at least the package manager is complete. Nonetheless, there were plenty of improvements to zig build this release cycle:

The most notable changes to zig build have to do with the new Target Details. See that section for how to use the new setTarget API.

One new trick that may be useful to Windows developers is to set the default target to be native-native-gnu. This will use the native OS and version range as well as the native CPU, but take advantage of mingw-w64 rather than trying to integrate with system MSVC. This is more likely to "just work" for all your project contributors, because it eliminates a problematic system dependency.

Additionally the following improvements were made:

Thanks to Benjamin Feng, David Cao, Layne Gustafson, LemonBoy, Michael Dusan, Michaël Larouche, Nick Erdmann, Noam Preil, Sahnvour, Timon Kruiper, Valentin Anger, dbandstra, emekoi, frmdstryr, meme, mogud, pwzk, stratact, syscall0, and xackus for related contributions.

zig fmt §

In this release, zig fmt performs a few automatic syntax upgrades for you, for example, renaming @typeOf to @TypeOf, as well as updating to the new callconv syntax.

Thanks to LemonBoy, Vexu, Robin Voetter, Brendan Hansknecht, Michael Raymond, and xackus for contributions to zig fmt.

zig cc §

Many people discovered this feature from the blog post, `zig cc`: a Powerful Drop-In Replacement for GCC/Clang.

In summary, since Zig links against libclang, Zig has the ability to act as a C compiler. And since Zig ships with libc, it has the ability to act as a cross-compiling C compiler. This feature has been available since 0.4.0, however, what's new is the sub-command, zig cc, which has the ability to parse C compiler flags.

In this release, zig cc has full compatibility with Clang's command line options. Clang is not invoked directly; some components are replaced with Zig's own. For example, Zig provides all the include paths for libc, and acts as the linker driver. Zig translates the semantics of the arguments to its own internal build logic. Clang options that Zig is not aware of are forwarded to Clang directly. Some parameters are handled specially.

Since the writing of the blog post a few weeks ago, this feature has been further improved with more flag integration, as well as the ability to provide -lc++. The sub-command zig c++ is added for convenience.

Yes, that's right, Zig now acts as a C++ cross-compiler as well.

hello.cpp

#include <iostream>

int main() {
    std::cout << "Hello World!" << std::endl;
    return 0;
}

My host is currently x86-64 linux, but I'll build it for Windows as well as RISC-V:

$ zig c++ -o hello hello.cpp -lc
$ ./hello
Hello World!
$ zig c++ -o hello hello.cpp -lc -target x86_64-windows-gnu
$ wine64 hello.exe
Hello World!
$ zig c++ -o hello hello.cpp -lc -target riscv64-linux
$ qemu-riscv64 ./hello
Hello World!

Thanks to this new ability, Zig is now able to bootstrap itself. Bootstrapping is not to be confused with self-hosting.

I forgot to mention in the blog post that when you don't pass any optimization flags to zig cc, Zig determines that Debug Mode is appropriate, and enables Clang's UBSAN.

People are finding out now that their C code has undefined behavior: #4830 #4965

zig cc is intentionally an interface to the Zig compiler, a bit higher level than using Clang directly. With no optimization flags specified, zig cc infers debug mode. As you know from writing Zig code, debug mode has safety checks to prevent undefined behavior at runtime. This applies for C code as well, taking advantage of clang's UBSAN. The fact that debug mode is "default" is entirely intentional. I expect this to identify many bugs in existing codebases as people use zig cc out of convenience or curiosity and their code gets vetted by UBSAN for the first time.

Note that the presence of -O2,-O3 will cause zig to select release-fast, -Os will cause zig to select release-small, and optimization flags plus -fsanitize=undefined will cause zig to select release-safe.

I wrote a FAQ entry so that people can easily link to it to help explain when this situation comes up: Why do I get illegal instruction when using with zig cc to build C code?

So - is it production ready?

As long as you are aware of the open issues, and you have tested zig cc for your use case, and it successfully builds your project, then it should be safe to use zig cc with 0.6.0. It's not expected for this feature to change much; it is already very nearly in its final form.

However, this is not a guarantee. Until Zig 1.0, the project reserves the right to make breaking changes as necessary.

Nim has added zig cc as one of the C compiler backend options.

Thank you to Michael Dusan and Ryan Liptak for contributions related to this feature.

libc §

Because cross-compiling is a first-class use case, Zig provides libc whenever possible, rather than depending on the host libc.

musl 1.2.0 §

Zig ships with the source code to musl. When the musl C ABI is selected, Zig builds musl from source for the selected target.

This release updates the bundled musl source code to v1.2.0.

With this release, Zig no longer has any patches against upstream.

glibc 2.31 §

Zig gains the ability to target glibc 2.31 in addition to the other 41 glibc versions.

In this release, Zig's glibc support is improved to additionally provide -lutil and symbols provided by the dynamic linker. (#4748)

mingw-w64 7.0.0 §

Zig ships with the source code to mingw-w64. When targeting *-windows-gnu and linking against libc, Zig builds mingw-w64 from source for the selected target.

This release updates the bundled mingw-w64 source code to v7.0.0.

Additionally:

C Translation §

During this release cycle, all of the Clang C++ API that Zig's translate-c feature relies on has been extracted into:

Next, zig_clang.zig was added to match the zig_clang.h C ABI.

Once translate_c.cpp was fully updated to use zig_clang.h instead of the C++ includes, compilation of that source file went from 5 seconds to 0.5 seconds.

At this point the self-hosting effort began. I started src-self-hosted/translate-c.zig and hooked up the translate-c test suite to support adding cases that are intended to pass in the new implementation.

Slowly the contributions started coming in and gradually improving self-hosted translate-c, and it started passing more and more test cases.

After a few individual contributions, Vexu got the hang of things, and finished the entire self-hosted implementation of translate-c, ultimately deleting the 5,000 line C++ implementation in one final blow.

All the previous test cases pass now, plus new ones, and there is even a new kind of test, zig build test-run-translated-c, which attempts to compile and run the Zig code that was translated from C code.

Vexu didn't stop there. He implemented a full C tokenizer and a partial C AST parser, which are both now available in the Standard Library in the std.c namespace. As a result, Zig's support for macro translation is much improved in 0.6.0.

This is as big deal for Self-Hosted Compiler Progress, because it means that contributions to Zig's translate-c feature apply not only to the stage1 compiler, but to the (incomplete) self-hosted compiler as well.

I won't list translate-c improvements or bug fixes because they are too numerous, but the following things stick out:

Additional thanks to LemonBoy, Merlyn Morgan-Graham, Feix Weiglhofer, Josh Wolfe, Lachlan Easton, Layne Gustafson, Michael Dusan, Rocknest, Tadeo Kondrak, frmdstryr, travisstaloch, and via for contributions related to this feature.

Self-Hosted Compiler Progress §

Although there has been no progress during this release cycle directly on self-hosted semantic analysis and code generation, there has been significant progress towards self-hosting in other areas:

More and more of the compiler is moving to be implemented in Zig rather than in C++. The C++ percent is shrinking and Zig percent is increasing. However, bootstrapping remains forever a fixed, four-step process.

compiler-rt §

compiler-rt is the library that provides, for example, 64-bit integer multiplication for 32-bit architectures which do not have a machine code instruction for it. In the GNU world, it's called libgcc.

Unlike most compilers, which depend on a binary build of compiler-rt being installed alongside the compiler, Zig builds compiler-rt on-the-fly, from source, as needed for the target platform. This release saw some improvements to Zig's compiler-rt implementation.

LemonBoy spent two days on an epic bug sleuthing quest, which was only reproducible inside docker, on AArch64, and finally managed to solve the problem. An unrelated change to compiler-rt had exposed a latent bug, where __floatunditf was accidentally defined with parameter type u128 rather than u64. Typos on the ABI boundary are especially nasty!

In addition to that, LemonBoy contributed most of the improvements to compiler-rt during this release cycle.

With Zig 0.6.0, compiler-rt is much more complete, but not fully. There are some missing functions, and it's planned to do an audit before 1.0.

Additional thanks to Michael Dusan, daurnimator, Michaël Larouche, and Timon Kruiper for related contributions.

Test Coverage §

Zig uses a Continuous-Integration system to run Zig's test suite in various environments. In this release cycle, the system gained more test coverage:

Thanks to Michael Dusan for getting QEMU building statically into a nice tarball that the CI server can download, extract, and run. This allows us to use a newer QEMU version than available in the Ubuntu repositories, on which SIMD tests pass.

During this release cycle, many bugs were discovered to have been fixed as a side-effect of other changes. Rather than simply closing these bug reports, regression test cases were added for them.

C Translation now has a new category of test: "run-translated-c"

Benjamin Feng moved std.debug.global_allocator to std.testing.allocator, and improved it to add leak checking. This caught several leaks in the Standard Library. daurnimator helped migrate more tests to use std.testing.allocator.

More FreeBSD tests are now passing (#3210, #4455).

More RISC-V tests are now passing (#3338).

More tests are now passing since upgrading to LLVM 10 (#4492, #4724).

Memory Usage Reduction §

The large portion of Zig compiler that is (currently) implemented in C++ is memory hungry and our continuous-integration process exacerbates the issue to the point where the RAM/VRAM sizes of open-source CI providers are sometimes insufficient. Additionally as we add more features and tests, yet more memory pressure is applied.

The following bits are related to reducing the max RSS footprint of the compiler. Implementations by Andrew Kelley (ak) and Michael Dusan (md).

The main goal of memory usage reduction is to ensure that bootstrapping takes 3.5 GiB or less on the host system (#471).

Advanced IR Debugging §

Andrew added native-debug helper functions for the Zig compiler.

Print triplet of (source:line:col) by calling member function src() for types IrExecutable{Src,Gen}, AstNode, IrInst, IrInst{Src,Gen} .

Dump IR segment by calling member function dump() for types IrExecutable{Src,Gen}, AstNode, IrInst, IrInst{Src,Gen} .

Dump ZigValue type-as-string by calling member function dump .

When --verbose-ir is enabled, call dbg_ir_break(src_file_zig, line) to breakpoint inside ir_analyze() .

Call dbg_ir_clear() to clear all breakpoints.

Command Line Interface §

Added:

Removed:

Deprecated:

This will be removed once Compiler Explorer updates to the new CLI.

The command line interface now supports detecting native system headers and libraries (include/ and lib/ search paths). The implementation of this is self-hosted (#2041).

The error output is improved when an invalid CPU model or CPU feature is specified:

andy@ark ~> zig build-exe hello.zig -mcpu=bogus
Unknown CPU: 'bogus'
Available CPUs for architecture 'x86_64':
 amdfam10
 athlon
 athlon_4
 athlon_fx
 athlon_mp
 athlon_tbird
 athlon_xp
 athlon64
 athlon64_sse3
 atom
 barcelona
 bdver1
 bdver2
 bdver3
 bdver4
 bonnell
 broadwell
 btver1
 btver2
 c3
 c3_2
 cannonlake
 cascadelake
 cooperlake
 core_avx_i
 core_avx2
 core2
 corei7
 corei7_avx
 generic
 geode
 goldmont
 goldmont_plus
 haswell
 _i386
 _i486
 _i586
 _i686
 icelake_client
 icelake_server
 ivybridge
 k6
 k6_2
 k6_3
 k8
 k8_sse3
 knl
 knm
 lakemont
 nehalem
 nocona
 opteron
 opteron_sse3
 penryn
 pentium
 pentium_m
 pentium_mmx
 pentium2
 pentium3
 pentium3m
 pentium4
 pentium4m
 pentiumpro
 prescott
 sandybridge
 silvermont
 skx
 skylake
 skylake_avx512
 slm
 tigerlake
 tremont
 westmere
 winchip_c6
 winchip2
 x86_64
 yonah
 znver1
 znver2
andy@ark ~> zig build-exe hello.zig -mcpu=x86_64+bogus
Unknown CPU feature: 'bogus'
Available CPU features for architecture 'x86_64':
 3dnow: Enable 3DNow! instructions
 3dnowa: Enable 3DNow! Athlon instructions
 64bit: Support 64-bit instructions
 adx: Support ADX instructions
 aes: Enable AES instructions
 avx: Enable AVX instructions
 avx2: Enable AVX2 instructions
 avx512bf16: Support bfloat16 floating point
 avx512bitalg: Enable AVX-512 Bit Algorithms
 avx512bw: Enable AVX-512 Byte and Word Instructions
 avx512cd: Enable AVX-512 Conflict Detection Instructions
 avx512dq: Enable AVX-512 Doubleword and Quadword Instructions
 avx512er: Enable AVX-512 Exponential and Reciprocal Instructions
 avx512f: Enable AVX-512 instructions
 avx512ifma: Enable AVX-512 Integer Fused Multiple-Add
 avx512pf: Enable AVX-512 PreFetch Instructions
 avx512vbmi: Enable AVX-512 Vector Byte Manipulation Instructions
 avx512vbmi2: Enable AVX-512 further Vector Byte Manipulation Instructions
 avx512vl: Enable AVX-512 Vector Length eXtensions
 avx512vnni: Enable AVX-512 Vector Neural Network Instructions
 avx512vp2intersect: Enable AVX-512 vp2intersect
 avx512vpopcntdq: Enable AVX-512 Population Count Instructions
 bmi: Support BMI instructions
 bmi2: Support BMI2 instructions
 branchfusion: CMP/TEST can be fused with conditional branches
 cldemote: Enable Cache Demote
 clflushopt: Flush A Cache Line Optimized
 clwb: Cache Line Write Back
 clzero: Enable Cache Line Zero
 cmov: Enable conditional move instructions
 cx16: 64-bit with cmpxchg16b
 cx8: Support CMPXCHG8B instructions
 enqcmd: Has ENQCMD instructions
 ermsb: REP MOVS/STOS are fast
 f16c: Support 16-bit floating point conversion instructions
 false_deps_lzcnt_tzcnt: LZCNT/TZCNT have a false dependency on dest register
 false_deps_popcnt: POPCNT has a false dependency on dest register
 fast_11bytenop: Target can quickly decode up to 11 byte NOPs
 fast_15bytenop: Target can quickly decode up to 15 byte NOPs
 fast_bextr: Indicates that the BEXTR instruction is implemented as a single uop with good throughput
 fast_gather: Indicates if gather is reasonably fast
 fast_hops: Prefer horizontal vector math instructions (haddp, phsub, etc.) over normal vector instructions with shuffles
 fast_lzcnt: LZCNT instructions are as fast as most simple integer ops
 fast_scalar_fsqrt: Scalar SQRT is fast (disable Newton-Raphson)
 fast_scalar_shift_masks: Prefer a left/right scalar logical shift pair over a shift+and pair
 fast_shld_rotate: SHLD can be used as a faster rotate
 fast_variable_shuffle: Shuffles with variable masks are fast
 fast_vector_fsqrt: Vector SQRT is fast (disable Newton-Raphson)
 fast_vector_shift_masks: Prefer a left/right vector logical shift pair over a shift+and pair
 fma: Enable three-operand fused multiple-add
 fma4: Enable four-operand fused multiple-add
 fsgsbase: Support FS/GS Base instructions
 fxsr: Support fxsave/fxrestore instructions
 gfni: Enable Galois Field Arithmetic Instructions
 idivl_to_divb: Use 8-bit divide for positive values less than 256
 idivq_to_divl: Use 32-bit divide for positive values less than 2^32
 invpcid: Invalidate Process-Context Identifier
 lea_sp: Use LEA for adjusting the stack pointer
 lea_uses_ag: LEA instruction needs inputs at AG stage
 lwp: Enable LWP instructions
 lzcnt: Support LZCNT instruction
 macrofusion: Various instructions can be fused with conditional branches
 merge_to_threeway_branch: Merge branches to a three-way conditional branch
 mmx: Enable MMX instructions
 movbe: Support MOVBE instruction
 movdir64b: Support movdir64b instruction
 movdiri: Support movdiri instruction
 mpx: Deprecated. Support MPX instructions
 mwaitx: Enable MONITORX/MWAITX timer functionality
 nopl: Enable NOPL instruction
 pad_short_functions: Pad short functions
 pclmul: Enable packed carry-less multiplication instructions
 pconfig: platform configuration instruction
 pku: Enable protection keys
 popcnt: Support POPCNT instruction
 prefer_128_bit: Prefer 128-bit AVX instructions
 prefer_256_bit: Prefer 256-bit AVX instructions
 prefer_mask_registers: Prefer AVX512 mask registers over PTEST/MOVMSK
 prefetchwt1: Prefetch with Intent to Write and T1 Hint
 prfchw: Support PRFCHW instructions
 ptwrite: Support ptwrite instruction
 rdpid: Support RDPID instructions
 rdrnd: Support RDRAND instruction
 rdseed: Support RDSEED instruction
 retpoline: Remove speculation of indirect branches from the generated code, either by avoiding them entirely or lowering them with a speculation blocking construct
 retpoline_external_thunk: When lowering an indirect call or branch using a `retpoline`, rely on the specified user provided thunk rather than emitting one ourselves. Only has effect when combined with some other retpoline feature
 retpoline_indirect_branches: Remove speculation of indirect branches from the generated code
 retpoline_indirect_calls: Remove speculation of indirect calls from the generated code
 rtm: Support RTM instructions
 sahf: Support LAHF and SAHF instructions
 sgx: Enable Software Guard Extensions
 sha: Enable SHA instructions
 shstk: Support CET Shadow-Stack instructions
 slow_3ops_lea: LEA instruction with 3 ops or certain registers is slow
 slow_incdec: INC and DEC instructions are slower than ADD and SUB
 slow_lea: LEA instruction with certain arguments is slow
 slow_pmaddwd: PMADDWD is slower than PMULLD
 slow_pmulld: PMULLD instruction is slow
 slow_shld: SHLD instruction is slow
 slow_two_mem_ops: Two memory operand instructions are slow
 slow_unaligned_mem_16: Slow unaligned 16-byte memory access
 slow_unaligned_mem_32: Slow unaligned 32-byte memory access
 soft_float: Use software floating point features
 sse: Enable SSE instructions
 sse_unaligned_mem: Allow unaligned memory operands with SSE instructions
 sse2: Enable SSE2 instructions
 sse3: Enable SSE3 instructions
 sse4_1: Enable SSE 4.1 instructions
 sse4_2: Enable SSE 4.2 instructions
 sse4a: Support SSE 4a instructions
 ssse3: Enable SSSE3 instructions
 tbm: Enable TBM instructions
 use_aa: Use alias analysis during codegen
 use_glm_div_sqrt_costs: Use Goldmont specific floating point div/sqrt costs
 vaes: Promote selected AES instructions to AVX512/AVX registers
 vpclmulqdq: Enable vpclmulqdq instructions
 vzeroupper: Should insert vzeroupper instructions
 waitpkg: Wait and pause enhancements
 wbnoinvd: Write Back No Invalidate
 x87: Enable X87 float instructions
 xop: Enable XOP instructions
 xsave: Support xsave instructions
 xsavec: Support xsavec instructions
 xsaveopt: Support xsaveopt instructions
 xsaves: Support xsaves instructions

Of course, this works for any architecture, not only x86_64.

Thanks Noam Preil, Christine Dodrill, David Cao, and Layne Gustafson for related contributions.

Miscellaneous Improvements §

Bug Fixes §

Full list of bug reports closed during this release cycle. Note: many bugs were both introduced and resolved within this release cycle. Listed below are fixed bugs that were not reported on the issue tracker.

Special thanks to LemonBoy, who solved a sizeable chunk of those issues, in many different parts of the Zig project.

This Release Contains Bugs §

Zig has known bugs and even some miscompilations.

Zig is immature. Even with Zig 0.6.0, working on a non-trivial project using Zig will likely require participating in the development process.

The first release to ship with no known bugs will be 1.0.0.

Please Welcome Vexu to the Core Zig Team §

I am pleased to announce our newest Zig team member, Vexu.

Vexu has shown continued dedication and discipline in contributions to the Zig programming language project. The quality of Vexu's work speaks for itself.

In addition, Vexu has proven to be a steadfast community leader, setting an example for how to treat others with kindness and respect.

I look forward to working with Vexu as we continue to push Zig toward 1.0.0 and beyond.

Roadmap §

According to the 0.5.0 Roadmap, the major theme of the 0.6.0 release was supposed to be Safety. I also wrote:

I expect to complete [Networking] along with at least an early prototype of the package manager during the next release cycle.

Clearly, this release cycle went in a different direction than planned. I realized that stabilizing the language is a top priority that everything else rests on. I also prioritized merging pull requests (at the time of writing, there are only 21 open pull requests, with the oldest one 36 days old), and unblocking contributors from accomplishing their goals.

The theme of the 0.7.0 release cycle will be stabilizing the language, creating a first draft of the language specification, and self-hosting the compiler.

It would be a major accomplishment if Zig 0.7.0 could ship with self-hosted instead of stage1.

Package Manager Status §

Having a package manager built into the Zig compiler is a long-anticipated feature. Zig 0.6.0 does not have this feature.

If the package manager works well, people will use it, which means building Zig projects will involve compiling more lines of Zig code, which means the Zig compiler must get faster, better at incremental compilation, and better at resource management.

Therefore, the package manager depends on finishing the self-hosted compiler, since it is planned to have these improved performance characteristics, while stage1 is not planned to have them.

C Header File Generation Status §

There were two improvements:

Thanks Sahnvour and mogud.

However, C header file generation is now disabled by default. The proof-of-concept is complete; but now it's a maintenance burden to implement this feature both in stage1 and in self-hosted.

The plan is to implement this feature in the self-hosted compiler, and then remove the feature from stage1, since it is not needed to bootstrap.

Accepted Proposals §

If you want more of a sense of the direction Zig is heading, you can look at the set of accepted proposals.

Active Open-Source Projects Using Zig §

Funding Status §

The Zig project is financially sustainable. It currently supports one full-time developer - yours truly, Andrew Kelley.

If you flip through the previous release notes, you can see the number of commits and number of contributors per release increasing super-linearly.

The project is succeeding!

Consequently, merging pull requests and providing troubleshooting, support, and moderation for the quickly-growing community creates a strong demand on time that is too much for just one person.

That is why I decided to start the Zig Software Foundation, a non-profit organization with the mission of raising the bar of software standards, ethics, and quality, and paying open source contributors for their valuable time.

I hope you will stay tuned for an official announcement about the ZSF, which I expect to happen within 6 months.

Thank You Sponsors! §

Special thanks to those who sponsor Zig. Because of you, Zig is driven by the open source community, rather than the goal of making profit. In particular, these fine folks sponsor Zig for $15/month or more: