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.
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.
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:
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.
Zig uses a Tier System to communicate the level of support for different targets. Notably, in this release:
| 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:
std.heap.page_allocator
gains a WebAssembly implementation.zig targets
is guaranteed to include this target.zig targets
will
display the target if it is available.--emit asm
and cannot emit object files.
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:
wWinMain
, wWinMainCRTStartup
,
and DllMain
are now recognized entrypoints. (#4376)Thanks Jared Miller, emekoi, syscall0, and LemonBoy for contributions related to this.
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 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.
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.
getauxval
. This caused SIGILL on armv7a-linux. (#4796)hash_const_val
. In some cases the compiler
was actually emitting an 64 bit signed multiplication, instead of a 32 bit unsigned one.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)
LemonBoy contributed MIPS fixes:
LemonBoy contributed NetBSD fixes: (#4793)
Nick Erdmann and Heppokoyuki contributed UEFI improvements:
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."
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.
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.
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
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.
||
).pub
syntax for container fields is removed.*[0]T
to E![]const T
is now allowed. This is an unambiguous, safe cast.asm
now accepts comptime-known values, rather than
requiring string literal syntax.?comptime_int
and
null
. (#2763)comptime
types and non comptime
types to same parameter.@typeOf
is renamed to @TypeOf
. zig fmt automatically performs the conversion, and the next release of Zig after this one will remove the automatic conversion.a else unreachable
with
comptime
a
to produce a
comptime
result.[*c]T
and ?[*:0]T
on fn
parameter. (#4176)@typeInfo
to lazily resolve declarations.
This way all the declarations in a namespace won't be resolved until the user actually
uses the declarations slice in the builtin TypeInfo union. (#2594, #3893, #4435)@ptrCast
supports casting a slice to a pointer.?[]T
and *[N]T
. (#4767)[]T
and *[N]T
. (#4766)Thanks to Vexu and LemonBoy for contributions related to the above list.
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);
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.
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.
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.
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.
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.
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{<PHONE_NUMBER>, -2, 30, 40 };
var x: @Vector(4, i32) = [4]i32{ 1,<PHONE_NUMBER>, 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.
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(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.
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.
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_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.
Thanks to Vexu:
@atomicStore
builtin.@cmpxchgWeak
, @cmpxchgStrong
, and
@atomicRmw
now support being evaluated in
comptime
code.//! 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_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).
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 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(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(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.
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
, 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.
Many deprecated builtins have been removed.
Thanks to Maciej Walczak for removing these and implementing the corresponding std lib functions:
@bytesToSlice
becomes mem.bytesAsSlice
@sliceToBytes
becomes mem.sliceAsBytes
Thanks to Vexu for removing these:
@typeId
becomes @typeInfo
tag-type@memberCount
becomes std.meta.fields(T).len
@memberName
becomes std.meta.fields(T)[i].name
@memberType
becomes std.meta.fields(T)[i].field_type
@ArgType
becomes @typeInfo(T).Fn.args[i].arg_type.?
@IntType
becomes std.meta.IntType
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.
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.
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.
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
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
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.
std.os.dup2
makes EBADF more obvious in stack traces.std.ChildProcess.spawn
now has a consistent error set across targets.std.io.getStdOut
and related functions no longer can error.
Thanks to the
Windows Process Environment Block,
it is possible to obtain handles
to the standard input, output, and error streams without the possibility of failure.std.math.tau
constant (equivalent to 2 * pi
).std.testing.expectEqual
to show differing pointer values, avoiding confusion when the values pointed to are the same.std.heap.direct_allocator
is renamed to std.heap.page_allocator
, to make it more clear that this is not an appropriate general-purpose allocator.std.sort.binarySearch
. (#4337)std.elf
API updated to remove redundant namespacing, and
integrate with std.Target.Arch
.std.testing.expectEqual
for tagged unions. (#3773)
std.math
: remove constants that should be expressions. There were
four cases where the value can be represented in fewer characters with
expressions, which will be guaranteed to happen at compile-time, and
have the same or better precision.
std.sort
:isSorted
.max
to accept const slices, and added tests.min
and max
to return ?T
.argMax
and argMin
which return indexes rather than values.std.fmt.ParseUnsignedError
is now public.std.io.BufferedInStream.readByte
,
speeding it up by ~75% (#3858).std.ChildProcess
to use eventfds on Linux
rather than pipe for communicating an error from child to parent process. (#819)std.unicode.utf8ToUtf16Le
to support surrogate pairs. (#3923)std.meta.TagPayloadType
to take the tag type of the union.std.os.memfd_create
. (#3687)std.os.getrusage
. (#3854)std.os.windows.WaitForSingleObject
.std.math.clamp
.std.rb
to make it thread-safe.std.mem.Allocator
interface to set
memory to undefined when freed (#4087). However note that it is
planned to revert this and implement this as part of allocator implementations rather than the interface.
writeByteNTimes
faster and leaner.std.heap.ArenaAllocator.deinit
not require a mutable reference.std.os.faccessat
for Windows.std.time.Timer.lap
to only
read system time once. (#4533)std.Thread.cpuCount
on Windows uses the PEB,
rather than calling GetSystemInfo from kernel32.dll. Also remove OutOfMemory
from the error set.std.unicode.utf8ToUtf16LeStringLiteral
which can be used to provide convenient "wide string literals": w("foo")
std.os.fnctl
math.Order
rather than i8
(#4791)std.os.getenv
and related functions to be
ascii-case-insensitive on Windows. (#4608)std.big.rational.gcd
to std.big.int.gcd
std.zig.parseStringLiteral
to support
hex and unicode escapes. (#4678)std.io.readLine
is removed.
std.fs.File.read
.
std.os.execvpe
related functions support optionally expanding argv[0] into the absolute
path based on the PATH environment variable. This can be useful to work around a third
party program which improperly uses argv[0]
to find the path to its own executable.
std.os.execve
had the wrong name; it should have been
std.os.execvpe
. This is now corrected. It is also
improved to handle ENOTDIR
(#3415).
std.os.execveZ
which does not look at PATH, and uses
null terminated parameters, matching POSIX ABIs. It does not require an allocator.
std.os.execvpeZ
, which is like execvpe
except it uses null terminated parameters, matching POSIX ABIs, and thus
does not require an allocator.
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:
pthread_cond_t
for synchronization
which also supports static initialization. This fares better for longer blocking
critical sections compared to the spinlock default of the current std.Mutex implementation.
linux_futex
so not many improvements
besides adaptive spinning there
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.
LemonBoy made a number of improvements to Zig's debug info code:
leb
module available to non-std codeAdditionally, Rocknest brought Windows segfault handler code on par with POSIX. (#4319)
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.
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.
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:
std.fs.Dir
namespace.
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:
error.OutOfMemory
, or in the case of deleting a file, error.NoSpaceLeft
.Thank you to contributors LeRoyce Pearson, Jonathan S, daurnimator, LemonBoy, Terin Stock, dimenus, and stratact.
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.
std.json.WriteStream.writeJson
.union(enum)
.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.
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:
other
tag to std.Target.Ospub fn main()
)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
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.
Iterator API is removed from std.ArrayList, since it is now possible to use
a for
loop on the items
field.
Additionally:
appendNTimes
(#4460).toSlice
and
toSliceConst
, and at
. Instead,
access the items
slice directly.outStream
- creates a stream to append to the ArrayList.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.
std.mem.len
no longer takes a type parameter, and
uses Sentinel-Terminated Pointers.std.mem.span
.std.mem.spanZ
.std.mem.zeroes
which zero-initializes types which have
well-defined memory layouts. (#4544)std.mem.Allocator.allocSentinel
.std.mem.indexOfSentinel
.std.mem.separate
to std.mem.split
.std.mem.Compare
to std.math.Order
.std.mem.compare
to std.mem.order
.std.math.order
.std.mem.toSlice
.std.mem.toSliceConst
.Thanks to Bas van den Berg, Emeka Nkurumeh, Jonathan Marler, Michaël Larouche, Sebastian, Timon Kruiper, daurnimator, and xackus for related contributions.
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.
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.
if
is an expression.allowzero
interaction with optional pointers.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.
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:
std.atomic.Queue
.Thank you Rocknest, Timon Kruiper, Henry Wu, Felix Queißner, Vexu, dtw-waleee, pfg, and xackus for related contributions.
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:
@intToPtr
with misaligned address.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
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:
addIncludeDir
does -I
instead of -isystem
.--
can be used to pass args to zig build commands.-D
now supports "list" type options.exe.installRaw("kernel.bin");
where exe is a LibExeObjStep. (#2826)RunStep.stdin_behavior = .Ignore
.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.
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
.
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.
Because cross-compiling is a first-class use case, Zig provides libc whenever possible, rather than depending on the host libc.
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.
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)
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:
During this release cycle, all of the Clang C++ API that Zig's translate-c feature relies on has been extracted into:
zig_clang.h
(C)zig_clang.cpp
(C++)
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:
@cImport
now automatically caches results, so subsequent builds are fast.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.
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:
zig targets
is now self-hosted, and outputs JSON.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 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.

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.
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).
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).
-DZIG_ENABLE_MEM_PROFILE
accepts
command-line option -fmem-report to produce a list of objects
allocated by compiler code. Some useful invocations:
./zig test ../lib/std/std.zig --cache off -fmem-report
/usr/bin/time -v ./zig test ../lib/std/std.zig --cache off -fmem-report
IrInstruction
by 8 bytes on 64 bit targets.
(#3482) RSS savings of ~3%
ZigValue
from IrInstruction
.
Add const interning for 1-possible-value types.
(#3502) RSS savings of ~6%
ConstGlobalRefs
into ZigValue
.
(#3817) RSS savings of ~1.1%
IrAnalyze
sometimes.
RSS savings of ~1%
IrInstruction
into IrInst
, IrInstSrc
, IrInstGen
.
This makes it so that less memory is used for IR instructions, as well
as catching bugs when one expected one kind of instruction and received
the other.
(#4290)
mem::Allocator
interfaceheap::CAllocator
impl with global heap::c_allocator
heap::ArenaAllocator
implmem::List
takes explicit Allocator&
parametermem::HashMap
takes explicit Allocator&
parameterCodegen.pass1_arena
and use for all ZigValue
allocsCodegen.pass1_arena
early in zig_llvm_emit_output()
The main goal of memory usage reduction is to ensure that bootstrapping takes 3.5 GiB or less on the host system (#471).
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.
Added:
--eh-frame-hdr
- enable C++ exception handling by passing --eh-frame-hdr to link-fsanitize-c
- enable C undefined behavior detection in unsafe builds-fno-sanitize-c
- disable C undefined behavior detection in safe builds-fmem-report
- print memory usage diagnostics-fdump-analysis
- write analysis.json file with type information-femit-docs
- create a docs/ dir with html documentation-fno-emit-docs
- do not produce docs/ dir with html documentation-femit-bin
- (default) output machine code-fno-emit-bin
- do not output machine code-femit-asm
- output .s (assembly code)-fno-emit-asm
- (default) do not output .s (assembly code)-femit-llvm-ir
- produce a .ll file with LLVM IR-fno-emit-llvm-ir
- (default) do not produce a .ll file with LLVM IR-femit-h
- generate a C header file (.h)-fno-emit-h
- (default) do not generate a C header file (.h)--verbose-llvm-cpu-features
- enable compiler debug output for LLVM CPU features-I[dir]
- add directory to include search path-mcpu [cpu]
- specify target CPU and feature set-code-model [default|tiny|small|kernel|medium|large]
- set target code model--test-evented-io
- runs the test in evented I/O modeRemoved:
-target-glibc [version]
- now specified as part of target triple-mios-version-min [ver]
- obsoleted by OS Version Ranges-mmacosx-version-min [ver]
- obsoleted by OS Version RangesDeprecated:
--emit [asm|bin|llvm-ir]
- prefer the new -femit-* or -fno-emit* options above.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.
-I
command line parameter.--eh-frame-hdr
CLI option. (#3981)bool
. (#2685)"builtin"
import with regards to usingnamespace
.native_libc.txt
file in zig-cache, and thus
there is no longer a possibility for this file to become stale and cause problems.
The libc installation path detection code is always run when needed. (#3975, #4186, #4940)make
without
being required to make install
. On Windows the INSTALL target is still a
required part of the development process.@tagName
to work on enum literals. (#4214)zig libc
config.zig BUILD_INFO
hack is removed.
Rather than stuffing configuration information into the Zig binary, the
build script reads it from config.h. This solves a problem for package
maintainers and improves the use case of deterministic builds. (#3758)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.
.*=
.std.event.Channel
.std.math.big.Int.toString
. (#3992)switch
range endpoints. (#4172)std.child_process.ChildProcess.spawnWindows
when looking in PATH
environment variable, it applied cwd+app_name instead of just using the app_name.std.http.headers
where .put captures user-held variable.isAbsolute
path functions. Empty
strings are no longer considered absolute paths. (#4382)openElfDebugInfo
.allowzero
slices. Instead they insert a runtime assertion. (#4462)usize
. (#4169)std.fmt.parseFloat
(#4845)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.
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.
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.
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.
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.
If you want more of a sense of the direction Zig is heading, you can look at the set of accepted proposals.
@embedFile
.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.
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: