Zig is a general-purpose programming language designed for robustness, optimality, and maintainability. Zig is aggressively pursuing its goal of overthrowing C as the de facto language for system programming. Zig intends to be so practical that people find themselves using it, because it "just works".
This release features 6 months of work and changes from 67 different contributors, spread among 1541 commits.
Special thanks to my sponsors who provide financial support. You're making Zig sustainable.
This release of Zig upgrades to LLVM 9. Zig operates in lockstep with LLVM; Zig 0.5.0 is not compatible with LLVM 8.
Notably this means that Zig now has RISC-V Support.
Zig also gains emscripten as a target OS. emscripten cannot self-host yet, but when it can, it will be interesting to explore this as an option for a Zig-in-the-browser sandbox, using WebAssembly.
A support table for master branch can be found on the home page. Here the support table for 0.5.0 is reproduced:
free standing | Linux 3.16+ | macOS 10.13+ | Windows 7+ | FreeBSD 12.0+ | NetBSD 8.0+ | UEFI | WASI | Android | |
---|---|---|---|---|---|---|---|---|---|
x86_64 | Tier 2 | Tier 1 | Tier 1 | Tier 1 | Tier 2 | Tier 2 | Tier 2 | N/A | Tier 2 |
wasm32 | Tier 2 | N/A | N/A | N/A | N/A | N/A | N/A | Tier 2 | N/A |
arm64 | Tier 2 | Tier 2 | N/A | Tier 3 | Tier 3 | Tier 3 | Tier 3 | N/A | Tier 2 |
arm32 | Tier 2 | Tier 2 | N/A | Tier 3 | Tier 3 | Tier 3 | Tier 3 | N/A | Tier 2 |
mips32 LE | Tier 2 | Tier 2 | N/A | N/A | Tier 3 | Tier 3 | N/A | N/A | N/A |
i386 | Tier 2 | Tier 3 | Tier 4 | Tier 3 | Tier 3 | Tier 3 | Tier 3 | N/A | Tier 2 |
bpf | Tier 3 | Tier 3 | N/A | N/A | Tier 3 | Tier 3 | N/A | N/A | N/A |
hexagon | Tier 3 | Tier 3 | N/A | N/A | Tier 3 | Tier 3 | N/A | N/A | N/A |
mips32 BE | Tier 3 | Tier 3 | N/A | N/A | Tier 3 | Tier 3 | N/A | N/A | N/A |
mips64 | Tier 3 | Tier 3 | N/A | N/A | Tier 3 | Tier 3 | N/A | N/A | N/A |
amdgcn | Tier 3 | Tier 3 | N/A | N/A | Tier 3 | Tier 3 | N/A | N/A | N/A |
sparc | Tier 3 | Tier 3 | N/A | N/A | Tier 3 | Tier 3 | N/A | N/A | N/A |
s390x | Tier 3 | Tier 3 | N/A | N/A | Tier 3 | Tier 3 | N/A | N/A | N/A |
lanai | Tier 3 | Tier 3 | N/A | N/A | Tier 3 | Tier 3 | N/A | N/A | N/A |
powerpc32 | Tier 3 | Tier 3 | Tier 4 | N/A | Tier 3 | Tier 3 | N/A | N/A | N/A |
powerpc64 | Tier 3 | Tier 3 | Tier 4 | N/A | Tier 3 | Tier 3 | N/A | N/A | N/A |
wasm64 | Tier 4 | N/A | N/A | N/A | N/A | N/A | N/A | Tier 4 | N/A |
avr | Tier 4 | Tier 4 | N/A | N/A | Tier 4 | Tier 4 | N/A | N/A | N/A |
riscv32 | Tier 4 | Tier 4 | N/A | N/A | Tier 4 | Tier 4 | Tier 4 | N/A | N/A |
riscv64 | Tier 4 | Tier 4 | N/A | N/A | Tier 4 | Tier 4 | Tier 4 | N/A | N/A |
xcore | Tier 4 | Tier 4 | N/A | N/A | Tier 4 | Tier 4 | N/A | N/A | N/A |
nvptx | Tier 4 | Tier 4 | N/A | N/A | Tier 4 | Tier 4 | N/A | N/A | N/A |
msp430 | Tier 4 | Tier 4 | N/A | N/A | Tier 4 | Tier 4 | N/A | N/A | N/A |
r600 | Tier 4 | Tier 4 | N/A | N/A | Tier 4 | Tier 4 | N/A | N/A | N/A |
arc | Tier 4 | Tier 4 | N/A | N/A | Tier 4 | Tier 4 | N/A | N/A | N/A |
tce | Tier 4 | Tier 4 | N/A | N/A | Tier 4 | Tier 4 | N/A | N/A | N/A |
le | Tier 4 | Tier 4 | N/A | N/A | Tier 4 | Tier 4 | N/A | N/A | N/A |
amdil | Tier 4 | Tier 4 | N/A | N/A | Tier 4 | Tier 4 | N/A | N/A | N/A |
hsail | Tier 4 | Tier 4 | N/A | N/A | Tier 4 | Tier 4 | N/A | N/A | N/A |
spir | Tier 4 | Tier 4 | N/A | N/A | Tier 4 | Tier 4 | N/A | N/A | N/A |
kalimba | Tier 4 | Tier 4 | N/A | N/A | Tier 4 | Tier 4 | N/A | N/A | N/A |
shave | Tier 4 | Tier 4 | N/A | N/A | Tier 4 | Tier 4 | N/A | N/A | N/A |
renderscript | Tier 4 | Tier 4 | N/A | N/A | Tier 4 | Tier 4 | N/A | N/A | N/A |
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.This release updates to LLVM 9, musl 1.1.23 with patches, and glibc 2.30. This plus updates to the Standard Library means that Zig's (64-bit) RISC-V support has gone from Tier 4 Support to Tier 3 Support in this release.
RISC-V is a very flexible target, with features such as atomics, and even integer multiplication
being optional. Since Zig does not yet have
ability to specify target CPU features,
the default set of cross-compilation features are "+a,+c,+d,+f,+m,+relax"
,
which matches Clang.
The RISC-V target does not yet pass Zig's test suite:
The next step for all these issues is to create test case reductions and then file upstream bug reports.
However, I did work with Rich Felker to get musl building with Clang for the RISC-V target, which means that we can do this:
hello.c
#include <stdio.h>
int main(int argc, char **argv) {
printf("Hello world\n");
return 0;
}
$ zig build-exe --c-source hello.c -lc -target riscv64-linux-musl
$ qemu-riscv64 ./hello
Hello world
Zig 0.5.0 carries a few patches to musl which makes this work. A new musl release is expected soon which contains these patches.
To be clear - the above simple example also works in Zig - it's just that all the language features
such as f16
are not working, and there is no automated Test Coverage
for this target.
hello.zig
const std = @import("std");
pub fn main() void {
std.debug.warn("Hello from zig\n");
}
$ zig build-exe hello.zig -target riscv64-linux
$ qemu-riscv64 ./hello
Hello from zig
Although glibc 2.30 gained RISC-V support, Zig is not able to build glibc for this target yet. See #3340 for more details. Looks like it could be as simple as importing a couple more .h files from the glibc source tree.
LemonBoy worked on Standard Library support for aarch64 during this release cycle:
epoll_*
struct on non x86_64 arches.After this work, and improvements made to Test Coverage, the following targets are now covered by the Zig test suite:
This test coverage led to the following bug fixes:
undefined
ptr. However, due to LLVM miscompiling trivial switch for AArch64, some failing tests are disabled, which means 64-bit ARM remains a Tier 2 Support target. The good news is we filed an LLVM bug report, and it has already been solved in LLVM trunk, scheduled to be included in LLVM 9.0.1.
After that, the only remaining issues standing in the way of Tier 1 Support for ARM 64-bit (aarch64) Linux are:
arm64-test.zig
const builtin = @import("builtin");
const std = @import("std");
const assert = std.debug.assert;
test "cross compiled unit test" {
assert(builtin.arch == .aarch64);
}
$ zig test arm64-test.zig -target aarch64v8-linux --test-cmd qemu-aarch64 --test-cmd-bin
1/1 test "cross compiled unit test"...OK
All tests passed.
Thanks to compiler-rt improvements by vegecode and LemonBoy, Zig's 32-bit ARM support is much stronger in version 0.5.0.
Alongside these efforts, LemonBoy improved the Standard Library by making
I/O offsets and sizes u64
instead of usize
,
decoupling the concepts of address-space size and file size. This solved many compile errors
when trying to target 32-bit ARM, as well as any other 32-bit architecture. #637
He also made several improvements to Thread Local Storage for 32-bit ARM.
Robin Voetter joined the Zig community during this release cycle, and hammered away at the Standard Library:
std.os.linux.mmap
use SYS_mmap2
if it exists.sys_*stat*64
instead of sys_*stat*
where appropriate.After all these improvements, 32-bit ARM support is leveled-up to Tier 2 Support. Along with improvements made to Test Coverage, the following targets are now covered by the Zig test suite:
arm32-test.zig
const builtin = @import("builtin");
const std = @import("std");
const assert = std.debug.assert;
test "cross compiled unit test" {
assert(builtin.arch == .arm);
}
$ zig test arm32-test.zig -target armv8-linux --test-cmd qemu-arm --test-cmd-bin
1/1 test "cross compiled unit test"...OK
All tests passed.
After updating to musl 1.1.23, Zig's clone
on arm32 is updated to
latest musl implementation.
Remaining issues to solve in order to achieve Tier 1 Support for ARM 32-bit Linux:
Although Zig does not officially support MSYS2 as a host target, emekoi has dutifully maintained unofficial support. Thanks to emekoi's efforts, one can build and run the stage1 C++ compiler of Zig in an MSYS2 environment.
Note: sometimes "MinGW" is used as a shorthand to mean "MSYS2". However, it is not to be confused with mingw-w64, or with the unrelated project, MinGW. MinGW-w64 is a fork of MinGW, which adds support for more architectures (such as 64-bit Windows) and more system APIs. When someone says "MinGW", it's almost certain they either mean "MSYS2" (which is based on MinGW-w64) or "MinGW-w64" instead.
Here is the list of things emekoi did to maintain unofficial support for MSYS2:
stratact made the following changes:
<stdint.h>
include for uint8_t
type declaration.dl_iterate_phdr
function for FreeBSD.This combined with disabling some of the failing standard library tests for FreeBSD, stratact was able to enable more Test Coverage for FreeBSD. Now the Continuous Integration server runs 7 additional kinds of tests from the test suite, instead of only the behavior tests.
stratact reports all tests passing locally, however, we have run into memory limits of SourceHut, which is the service used to run FreeBSD tests. Drew DeVault has understandably denied our request for more RAM, and so we are left with disabled test coverage until Zig can finish self-hosting, or improve the memory usage of the C++ stage1 compiler.
The set of remaining issues until Tier 1 FreeBSD Support for x86_64 is now:
During the 0.5.0 release cycle, Shritesh Bhattarai joined the Zig community and made significant contributions to Zig's WebAssembly and WASI (Web Assembly System Interface) support.
He got compiler-rt working and tweaked the target settings such as:
Thanks to this as well as Shritesh adding basic standard library support for the WASI target, as well as improving the linker settings that Zig uses, WebAssembly and WASI are now Tier 2 Support targets!
As a demo, Shritesh created zigfmt-web, which
is a web page that will run zig fmt
on a block of code, using the same
implementation as official zig fmt.
Shritesh created a basic allocator intended to be used on the WebAssembly target,
std.heap.wasm_allocator
. This uses the WebAssembly intrinsics to request memory
from the host, and is not capable of freeing memory. The standard library does not yet have
an allocator for WebAssembly that can reclaim freed memory. See
zee_alloc for a community project
attempting to solve this use case.
Shritesh also created a demo of Zig interacting with the DOM via JS (source).
Other miscellaneous improvements to WebAssembly:
std.os.page_size
for WebAssemblyZig now provides a Freestanding libc, which is available when linking libc for the WebAssembly target. It is not yet fully complete, but you can get a sense of the use case for it with this demo project: lua-in-the-browser
This use case led to several improvements to Zig's WebAssembly support:
build-exe
is for executables which have a main()
.
build-lib
is for building libraries of functions to use from,
for example, a web browser environment.--export-all
for libraries when there are any
C objects because we have no way to detect the list of exports
when compiling C code.--no-entry
to the linker for executables.
If you want --no-entry
then use build-lib
.build-exe
does include the startup code that supplies _start
for the
wasm32-freestanding
target. Previously this did not occur because
of logic excluding "freestanding".build-lib
for wasm32-freestanding
target gets
linked by LLD. To avoid infinite recursion, compiler-rt and
Freestanding libc are built as objects rather than libraries.build-lib foo.zig
producing "libfoo.a", now it produces "foo.wasm"._start
symbol for wasm when linking libc.Zig is particularly well suited to creating reasonably small & fast WebAssembly binaries. Here are some demos of WebAssembly projects from Zig community members:
Nick Erdmann has been reading the UEFI specification and improving Zig support for this target.
Zig's Standard Library now integrates more cleanly with UEFI, and other things now "just work" such as PDB files and 0x0 addresses.
Many of the UEFI protocol definitions are now available in std.os.uefi.protocols
.
Nick has clean and well-organized demo projects which serve as resources to help others learn how to do UEFI programming:
Matthew Iannucci added initial support for iOS targets (#2237).
However iOS remains a Tier 3 Support target. There are no known active Zig projects targeting iOS.
LemonBoy implemented Thread Local Storage for architectures that have thread pointer offsets, such as mipsel. He updated the Standard Library with the Linux system bits for the mipsel architecture, and worked with musl upstream to get it patched enough to be able to successfully build with Clang for this target. Zig carries this patch in 0.5.0.
After these changes, MIPS now has Tier 2 Support! LemonBoy reports running a Zig binary on his router:
18:59 <TheLemonMan> just got a Zig binary running on my mips32 router, yay
These targets are now covered by the Zig test suite:
Aside from investigating mipsel-linux-gnu
, the only remaining issues standing in the way of
Tier 1 Support for MIPS Little-Endian Linux (mipsel-linux)
are:
mips-test.zig
const builtin = @import("builtin");
const std = @import("std");
const assert = std.debug.assert;
test "cross compiled unit test" {
assert(builtin.arch == .mipsel);
}
$ zig test mips-test.zig -target mipsel-linux --test-cmd qemu-mipsel --test-cmd-bin
1/1 test "cross compiled unit test"...OK
All tests passed.
meme joined the Zig community and contributed improvements
to the target aarch64-linux-android
. Thanks to their efforts, Zig now has
Tier 2 Support for Android.
Here's an example of building an Android executable with Zig:
hello_android.zig
const std = @import("std");
pub fn main() void {
std.debug.warn("Hello, Android!");
}
$ zig build-exe hello_android.zig -target aarch64-linux-android
$ file ./hello_android
./hello_android: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, with debug_info, not stripped
In this example, there is no libc dependency. However, Zig does know how to integrate
with Android's libc. The first step is to create a libc text file describing where various paths
are. One can obtain a template for this file by executing zig libc
. In this example,
I've taken the template and populated it based on the path to the Android NDK in my downloads
folder:
android_libc.txt
# The directory that contains `stdlib.h`.
# On POSIX-like systems, include directories be found with: `cc -E -Wp,-v -xc /dev/null`
include_dir=/home/andy/Downloads/android-ndk-r20/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include
# The system-specific include directory. May be the same as `include_dir`.
# On Windows it's the directory that includes `vcruntime.h`.
# On POSIX it's the directory that includes `sys/errno.h`.
sys_include_dir=/home/andy/Downloads/android-ndk-r20/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include
# The directory that contains `crt1.o` or `crt2.o`.
# On POSIX, can be found with `cc -print-file-name=crt1.o`.
# Not needed when targeting MacOS.
crt_dir=/home/andy/Downloads/android-ndk-r20/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/21
# The directory that contains `crtbegin.o`.
# On POSIX, can be found with `cc -print-file-name=crtbegin.o`.
# Not needed when targeting MacOS.
static_crt_dir=/home/andy/Downloads/android-ndk-r20/toolchains/llvm/prebuilt/linux-x86_64/lib/gcc/aarch64-linux-android/4.9.x
# The directory that contains `vcruntime.lib`.
# Only needed when targeting MSVC on Windows.
msvc_lib_dir=
# The directory that contains `kernel32.lib`.
# Only needed when targeting MSVC on Windows.
kernel32_lib_dir=
hello_libc.zig
const std = @import("std");
extern fn printf(msg: [*]const u8, ...) c_int;
pub fn main() void {
_ = printf(c"hello android libc\n");
}
$ zig build-exe hello_libc.zig -target aarch64-linux-android -lc --libc android_libc.txt
$ file ./hello_libc
./hello_libc: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /system/bin/linker64, with debug_info, not stripped
@typeInfo(Slice).Pointer.child
.c_void
pointer.*[N]T
to [*c]T
.anyerror
no longer a keyword. #2835comptime_int
now implicit casts to comptime_float
.noinline
keyword for function declarations.enum
default to u0
.
However, comptime_int
is still allowed as an explicit enum tag type. #2997
usingnamespace
is a top level declaration that imports all the public declarations of
the operand, which must be a struct
, union
, or
enum
, into the current scope:
usingnamespace.zig
usingnamespace @import("std");
test "using std namespace" {
debug.assert(true);
}
$ zig test usingnamespace.zig
1/1 test "using std namespace"...OK
All tests passed.
Instead of the above pattern, it is generally recommended to explicitly alias individual declarations.
However, usingnamespace
has an important use case when organizing the public
API of a file or package. For example, one might have c.zig
with all of the
C imports:
pub usingnamespace @cImport({
@cInclude("epoxy/gl.h");
@cInclude("GLFW/glfw3.h");
@cDefine("STBI_ONLY_PNG", "");
@cDefine("STBI_NO_STDIO", "");
@cInclude("stb_image.h");
});
The above example demonstrates using pub
to qualify the
usingnamespace
additionally makes the imported declarations
pub
. This can be used to forward declarations, giving precise control
over what declarations a given file exposes.
In Zig 0.4.0, this feature existed as use
, but it only worked at the top-level
scope, and only for structs. The feature was also considered unstable.
Thank you LemonBoy for fixing usingnamespace
outside the top-level scope, and
making it work with arbitrary structs.
In Zig 0.5.0, both use
and usingnamespace
are accepted, and
zig fmt automatically converts to the canonical syntax. The next release of Zig after this
one will remove the old syntax.
This feature is now stable and planned to be included in the language specification.
Zig now always respects threadlocal
for variables with external linkage.
Previously, if you had, for example:
extern "c" threadlocal var errno: c_int;
This would turn errno
into a normal variable for --single-threaded
builds. However for variables with external linkage, there is an ABI
to uphold.
This is needed to make errno work for DragonFly BSD. See #2381.
@hasField(comptime Container: type, comptime name: []const u8) bool
@hasDecl(comptime Container: type, comptime name: []const u8) bool
The new builtin function @hasField returns whether the field name of a struct, union, or enum exists. The result is a compile time boolean. It does not include functions, variables, or constants.
The new builtin function @hasDecl returns whether or not a struct, enum, or union has a declaration.
has_builtins.zig
const std = @import("std");
const assert = std.debug.assert;
const Foo = struct {
nope: i32,
pub var blah = "xxx";
const hi = 1;
};
test "@hasDecl and @hasField" {
assert(@hasDecl(Foo, "blah"));
// Even though `hi` is private, @hasDecl returns true because this test is
// in the same file scope as Foo. It would return false if Foo was declared
// in a different file.
assert(@hasDecl(Foo, "hi"));
// @hasDecl is for declarations; not fields.
assert(!@hasDecl(Foo, "nope"));
assert(!@hasDecl(Foo, "nope1234"));
assert(@hasField(Foo, "nope"));
}
$ zig test has_builtins.zig
1/1 test "@hasDecl and @hasField"...OK
All tests passed.
Thanks to Shawn Landden for initial implementation and documentation of @hasField.
When translating C code, Zig does not know whether pointer types
should be translated to *
or [*]
pointers. Instead, they
are translated to C Pointers.
As the documentation notes, this type is to be avoided whenever possible. The only valid reason for using a C pointer is in auto-generated code from translating C code.
Interfacing with C pointer types happens due to direct interop with translated .h files. It's always
a future possibility to rewrite the .h file in .zig to gain better type-safety. Previously,
optional syntax, such as if
, orelse
, null
,
and .?
did not work for C pointers. This would cause compile errors if
the type signature of the external function prototypes were improved to have the real pointer types
rather than C pointers.
Now, this syntax works, and so there is no penalty for starting out with auto-translated headers, and then later "upgrading" to better typed bindings.
getenv.zig
const getenv = @cImport(@cInclude("stdlib.h")).getenv;
// note: this is just a demo of C pointers with optional syntax.
// std.process has better API for getenv.
test "C pointers with optional syntax" {
const ptr1 = getenv(c"HOME").?; // don't do this 💥
const ptr2 = getenv(c"HOME") orelse return error.Homeless; // OK
if (getenv(c"HOME")) |ptr3| {
// also OK
}
const ptr4 = getenv(c"HOME");
if (ptr4 == null) {
// also works
}
}
$ zig test getenv.zig -lc
1/1 test "C pointers with optional syntax"...OK
All tests passed.
The auto-translated getenv
prototype looks like this:
pub extern fn getenv(__name: [*c]const u8) [*c]u8;
If we were to improve this prototype with correct pointer types, the test will still pass:
getenv2.zig
pub extern fn getenv(name: [*]const u8) ?[*]u8;
// note: this is just a demo of C pointers with optional syntax.
// std.process has better API for getenv.
test "C pointers with optional syntax" {
const ptr1 = getenv(c"HOME").?; // don't do this 💥
const ptr2 = getenv(c"HOME") orelse return error.Homeless; // OK
if (getenv(c"HOME")) |ptr3| {
// also OK
}
const ptr4 = getenv(c"HOME");
if (ptr4 == null) {
// also works
}
}
$ zig test getenv2.zig -lc
1/1 test "C pointers with optional syntax"...OK
All tests passed.
Using switch
on an
error set now provides a way
to capture an error value with a subset type:
switch_err_set_1.zig
const std = @import("std");
const os = std.os;
const Error = error{
AccessDenied,
FileTooBig,
IsDir,
SymLinkLoop,
ProcessFdQuotaExceeded,
NameTooLong,
SystemFdQuotaExceeded,
NoDevice,
FileNotFound,
SystemResources,
NoSpaceLeft,
NotDir,
PathAlreadyExists,
DeviceBusy,
InputOutput,
OperationAborted,
BrokenPipe,
Unexpected,
};
pub fn main() Error!void {
// open a normal, blocking file descriptor.
const fd = try os.open("/dev/urandom", os.O_RDONLY, 0);
defer os.close(fd);
// we did *not* use O_NONBLOCK, so the OS will not give us
// EWOULDBLOCK.
var buf: [100]u8 = undefined;
const nbytes = try readBlocking(fd, &buf);
}
/// Asserts that fd was opened in a blocking fashion.
fn readBlocking(fd: os.fd_t, buffer: []u8) !usize {
return std.os.read(fd, buffer) catch |err| switch (err) {
error.WouldBlock => unreachable, // Remove this to observe compile error
else => |e| return e,
};
}
$ zig build-exe switch_err_set_1.zig
$ ./switch_err_set_1
Here you can see that the program compiled just fine, even though error.WouldBlock
is not found in the error set. This is because the function readBlocking
switched on the error set, and handled the error.WouldBlock
case. This means
the error set type of value captured by the else
does not include the value
error.WouldBlock
.
In addition to this, Zig allows capturing the payload from multiple error set values:
switch_err_set_2.zig
const std = @import("std");
const os = std.os;
const Error = error{
AccessDenied,
FileTooBig,
IsDir,
SymLinkLoop,
ProcessFdQuotaExceeded,
NameTooLong,
SystemFdQuotaExceeded,
NoDevice,
FileNotFound,
SystemResources,
NoSpaceLeft,
NotDir,
PathAlreadyExists,
DeviceBusy,
OperationAborted,
BrokenPipe,
Unexpected,
};
pub fn main() Error!void {
// open a normal, blocking file descriptor.
const fd = try os.open("/dev/urandom", os.O_RDONLY, 0);
defer os.close(fd);
// we did *not* use O_NONBLOCK, so the OS will not give us
// EWOULDBLOCK.
var buf: [100]u8 = undefined;
const nbytes = try readBlocking(fd, &buf);
}
/// Asserts that fd was opened in a blocking fashion.
fn readBlocking(fd: os.fd_t, buffer: []u8) !usize {
return std.os.read(fd, buffer) catch |err| switch (err) {
error.WouldBlock, error.InputOutput => |e| {
std.debug.panic("unexpected: {}\n", e);
},
else => |e| return e,
};
}
$ zig build-exe switch_err_set_2.zig
$ ./switch_err_set_2
In this example, error.InputOutput
was lifted out of Error
since it is handled inside readBlocking
. The e
capture value
has type error{WouldBlock,InputOutput}
.
Shawn Landden improved the consistency of the names and parameters of bit manipulation intrinsics.
@bitreverse
to @bitReverse to adhere to
naming conventions.@bswap
to @byteSwap.@bitReverse(comptime T: type, integer: T) T
@byteSwap(comptime T: type, operand: T) T
@clz(comptime T: type, integer: T)
@ctz(comptime T: type, integer: T)
@popCount(comptime T: type, integer: T)
Zig no longer validates whether identifiers exist in dead comptime
branches:
dead_comptime_branch.zig
test "dead comptime branch" {
if (false) {
does_not_exist = thisFunctionAlsoDoesNotExist();
}
}
$ zig test dead_comptime_branch.zig
1/1 test "dead comptime branch"...OK
All tests passed.
This is counter-intuitive, but consider that the set of available identifiers may depend on
comptime
parameters, such as the target OS:
const builtin = @import("builtin");
usingnamespace switch (builtin.os) {
.windows => @import("windows_stuff.zig"),
else => @import("posix_stuff.zig"),
};
test "example" {
if (builtin.os == .windows) {
ExitProcess(0);
} else {
exit(0);
}
}
In practice, this has resulted in various code cleanups throughout the standard library.
Zig's lazy analysis, while convenient, surfaces the inherent problems of conditional compilation. See the related proposal: "multibuilds" - a plan to harmonize conditional compilation with compile errors, documentation, and IDEs
Each struct field may now have an expression indicating the default field value. Such expressions
are executed at comptime
, and allow the field to be omitted in a struct
literal expression:
default_fields.zig
const Foo = struct {
a: i32 = 1234,
b: i32,
};
test "default struct field values" {
const x = Foo{
.b = 5,
};
if (x.a + x.b != 1239) {
@compileError("it's even comptime known!");
}
}
$ zig test default_fields.zig
1/1 test "default struct field values"...OK
All tests passed.
The array literal syntax has changed, when inferring the size.
Old syntax:
[]i32{1, 2, 3}
New syntax:
[_]i32{1, 2, 3}
The previous syntax used to look too much like instantiating a slice. This caused all kinds of confusion. Now it's pretty clear that the type is an array.
#1797
The Root Source File
(in the case of build-exe
, the file with pub fn main
)
is now available to import anywhere, using @import("root")
.
Combined with @hasDecl,
this allows library code to support global configuration settings based on declarations in the
root source file.
The Standard Library takes advantage of this for several use cases. One example is
the Default Segfault Handler. It works like this (from std.debug
):
const root = @import("root");
/// Whether or not the current target can print useful debug information when a segfault occurs.
pub const have_segfault_handling_support = builtin.os == .windows or
(builtin.arch == builtin.Arch.x86_64 and builtin.os == .linux);
pub const enable_segfault_handler: bool = if (@hasDecl(root, "enable_segfault_handler"))
root.enable_segfault_handler
else
runtime_safety and have_segfault_handling_support;
pub fn maybeEnableSegfaultHandler() void {
if (enable_segfault_handler) {
std.debug.attachSegfaultHandler();
}
}
And then Zig's startup code calls std.debug.maybeEnableSegfaultHandler()
just before
calling main()
.
Another place this is used in the standard library is to decide a global "I/O mode", which is related to Async Functions.
This feature has the capability to be abused, and should be used with care. Any root source file declarations that can affect a library's behavior should be well-documented. When Zig gains documentation generation, the auto-generated docs will have the capability to enumerate all the places that depend on a root source file declaration.
Thank you emekoi for the initial implementation of this.
Zig now has @mulAdd, otherwise known as "fused-multiply-add".
@mulAdd(comptime T: type, a: T, b: T, c: T) T
Performs (a * b) + c
, except only rounds once, and is thus more accurate.
Additionally, some targets have a hardware instruction for this, making it potentially faster than
a userland implementation.
mul_add.zig
const std = @import("std");
test "@mulAdd" {
// In this example we use numbers small enough to avoid rounding errors that would occur
// without @mulAdd.
std.testing.expect(@mulAdd(f32, 2.0, 3.0, 4.0) == (2.0 * 3.0) + 4.0);
}
$ zig test mul_add.zig
1/1 test "@mulAdd"...OK
All tests passed.
Currently this instruction only works for floating point types, as well as
vectors of floating point types. However, there is
an open proposal
to make this work for any types that support *
and +
operators, such as integers. The proposal also suggests to remove the explicit type parameter requirement.
Thank you Shawn Landden for the initial implementation of this.
Shawn Landden added:
@sin(comptime T: type, value: T) T
@cos(comptime T: type, value: T) T
@exp(comptime T: type, value: T) T
@exp2(comptime T: type, value: T) T
@ln(comptime T: type, value: T) T
@log2(comptime T: type, value: T) T
@log10(comptime T: type, value: T) T
@fabs(comptime T: type, value: T) T
@floor(comptime T: type, value: T) T
@ceil(comptime T: type, value: T) T
@trunc(comptime T: type, value: T) T
@round(comptime T: type, value: T) T
These are builtin functions because some architectures have hardware instructions for these. Furthermore, because these functions are well-defined, the optimizer may sometimes be able to convert calls to these builtins into better forms.
It's planned for Zig to provide libmvec
in the future, and these functions will become
SIMD-capable.
What I'm calling Result Location Semantics was a large branch of Zig that fundamentally changed the way that expressions are semantically analyzed. This was the third attempt, which finally succeeded. I abandoned the first attempt after 1 week. The second attempt, which took place during the 0.4.0 release cycle, lasted 2 months, but again was regretfully abandoned. However, there were significant parts of the second attempt that landed in the eventual implementation.
During my work on this branch, the Zig community stepped up and continued to improve master branch all the while. You can observe this by seeing how many names are mentioned in these release notes. I am proud and grateful of the Zig community for this.
Although the implementation was difficult, the user-facing differences of Result Location Semantics are nearly impossible to detect. The main purpose was to pave the way for the redesign of Async Functions.
The main thing that this change does is semantically guarantee that no copying happens in expressions. As an example:
result_loc.zig
const std = @import("std");
const Object = struct {
tag: i32,
pt: [2]Point,
};
const Point = struct {
x: i32,
y: i32,
};
test "result location semantics" {
const result = if (condition()) foo(10) else bar();
std.testing.expect(result.tag == 10);
std.testing.expect(result.pt[0].x == 69);
std.testing.expect(result.pt[1].y == 420);
}
fn condition() bool {
return true;
}
fn foo(arg: i32) Object {
return baz(arg);
}
fn bar() Object {
return Object{
.tag = 1,
.pt = undefined,
};
}
fn baz(arg: i32) Object {
return Object{
.tag = arg,
.pt = [_]Point{ nice(), blazet() },
};
}
fn nice() Point {
return Point{
.x = 69,
.y = 69,
};
}
fn blazet() Point {
return Point{
.x = 420,
.y = 420,
};
}
$ zig test result_loc.zig
1/1 test "result location semantics"...OK
All tests passed.
The important thing to note here is that the functions nice()
and
blazet()
write directly to result in the
main test function. There are no intermediate copies, and this is semantically guaranteed
by the language.
With Zig's current semantics, it is actually not possible to observe the difference between 0.4.0 and 0.5.0 (except with async function calls). However, with future proposals to the language it would matter a great deal:
The point here is that initialization functions would be able to set up pointer references relative to the return value, and have the value be guaranteed to be valid.
Before moving on to the next section I want to say a huge thank you to Michael Dusan. This branch was a dizzying amount of effort, and towards the end of it, Michael started contributing. He created test case reductions and even solved some of the regressions, such as vector to array conversion not being aligned correctly. This was both unexpected and helpful. It made a serious difference in getting me through to the end of the branch, so that we could merge it into master.
The other thing that came out of this branch was preferring the "result type" to Peer Type Resolution:
result_loc_peer.zig
const std = @import("std");
const expect = std.testing.expect;
test "result location type resolution" {
var f = false;
var x: i32 = 0;
x = if (f) 1 else 2;
expect(x == 2);
}
$ zig test result_loc_peer.zig
1/1 test "result location type resolution"...OK
All tests passed.
Additionally:
@unionInit(comptime Union: type, comptime active_field_name: []const u8, init_expr) Union
This is the same thing as union initialization syntax, except that the field name is a comptime-known value rather than an identifier token.
@unionInit
forwards its result location to init_expr
.
Thank you Robert Scott for the initial implementation of @unionInit.
Zig's unicode escape syntax is changed to match most other popular programming languages.
Old syntax:
"\U01f4a9"
New syntax:
"\u{1f4a9}"
This matches JavaScript (since ES6), Lua (since 5.3), Swift (who swapped from our previous syntax!), and Rust.
Thank you daurnimator for doing the research on other languages, and Shawn Landden for the implementation.
It is
planned to additionally allow unicode escapes in character literals,
since character literals have type comptime_int
. That accepted proposal is marked
"Contributor Friendly" because it is limited in scope and/or knowledge of Zig internals.
async
functions have been completely reworked in Zig 0.5.0. Previously,
I was calling these "stackless coroutines". However I'm now avoiding the word "coroutine" since
it means different things to different people, and instead using the phrase "async functions".
In Zig 0.4.0, all async functions were generic across an allocator type, all async functions took an allocator parameter, and calling an async function could fail. Additionally, async functions were required to be annotated as such.
In Zig 0.5.0, calling an async function can no longer fail. The async function frame is provided
by the caller via Result Location Semantics, and can be in the caller's stack frame. Async
functions are no longer generic, and do not require the async
keyword.
Zig infers that a function is async
when it observes that the function contains
a suspension point. Async functions can be called the same as normal functions. A
function call of an async function is a suspend point.
When a regular function is called, a frame is pushed to the stack, the function runs until it reaches a return statement, and then the frame is popped from the stack. At the callsite, the following code does not run until the function returns.
An async function is a function whose callsite is split into an async
initiation,
followed by an await
completion. Its frame is
provided explicitly by the caller, and it can be suspended and resumed any number of times.
Here's a simple example of an async function:
async_fn.zig
const std = @import("std");
var frame: anyframe = undefined;
pub fn main() void {
std.debug.warn("begin main\n");
_ = async func();
std.debug.warn("resume func\n");
resume frame;
std.debug.warn("end main\n");
}
fn func() void {
std.debug.warn("begin func\n");
frame = @frame();
suspend;
std.debug.warn("end func\n");
}
$ zig build-exe async_fn.zig
$ ./async_fn
begin main
begin func
resume func
end func
end main
Here we have a seam between non-async (main) and async (func) code. A more typical usage of this feature:
typical_async_await.zig
const std = @import("std");
const expect = std.testing.expect;
// Try toggling these
const simulate_fail_download = false;
const simulate_fail_file = false;
const suspend_download = true;
const suspend_file = true;
pub fn main() void {
_ = async amainWrap();
// This simulates an event loop
if (suspend_file) {
resume global_file_frame;
}
if (suspend_download) {
resume global_download_frame;
}
}
fn amainWrap() void {
if (amain()) |_| {
expect(!simulate_fail_download);
expect(!simulate_fail_file);
} else |e| switch (e) {
error.NoResponse => expect(simulate_fail_download),
error.FileNotFound => expect(simulate_fail_file),
else => @panic("test failure"),
}
}
fn amain() !void {
const allocator = std.heap.direct_allocator;
var download_frame = async fetchUrl(allocator, "https://example.com/");
var download_awaited = false;
errdefer if (!download_awaited) {
if (await download_frame) |x| allocator.free(x) else |_| {}
};
var file_frame = async readFile(allocator, "something.txt");
var file_awaited = false;
errdefer if (!file_awaited) {
if (await file_frame) |x| allocator.free(x) else |_| {}
};
download_awaited = true;
const download_text = try await download_frame;
defer allocator.free(download_text);
file_awaited = true;
const file_text = try await file_frame;
defer allocator.free(file_text);
expect(std.mem.eql(u8, "expected download text", download_text));
expect(std.mem.eql(u8, "expected file text", file_text));
std.debug.warn("OK!\n");
}
var global_download_frame: anyframe = undefined;
fn fetchUrl(allocator: *std.mem.Allocator, url: []const u8) anyerror![]u8 {
const result = try std.mem.dupe(allocator, u8, "expected download text");
errdefer allocator.free(result);
if (suspend_download) {
suspend {
global_download_frame = @frame();
}
}
if (simulate_fail_download) return error.NoResponse;
std.debug.warn("fetchUrl returning\n");
return result;
}
var global_file_frame: anyframe = undefined;
fn readFile(allocator: *std.mem.Allocator, filename: []const u8) anyerror![]u8 {
const result = try std.mem.dupe(allocator, u8, "expected file text");
errdefer allocator.free(result);
if (suspend_file) {
suspend {
global_file_frame = @frame();
}
}
if (simulate_fail_file) return error.FileNotFound;
std.debug.warn("readFile returning\n");
return result;
}
$ zig build-exe typical_async_await.zig
$ ./typical_async_await
readFile returning
fetchUrl returning
OK!
The important thing to note here is that the async
/await
mechanism did not bring in a dependency on the host OS, and it did not bring in a dependency on
an allocator.
Now watch what happens when we do this:
const suspend_download = false;
const suspend_file = false;
$ zig build-exe typical_async_await.zig
$ ./typical_async_await
fetchUrl returning
readFile returning
OK!
It's the same output, except in reversed order. With these modifications, there are no async
functions in the entire program! The expression
async fetchUrl(allocator, "https://example.com/")
is evaluated as a normal,
blocking function, as is async readFile(allocator, "something.txt")
.
The await
s are no-ops.
The point here is that the amain
function, which is the demo of typical async/await usage,
works in both an async context and blocking context. The programmer was able to express the inherent
parallelism of the logic, without resorting to
function coloring.
There is admittedly a bit of boilerplate in the example. Here's the tracking issue for that.
Now for the related Standard Library updates:
This introduces the concept of "IO mode" which is configurable by the
Root Source File (e.g. next to
pub fn main
). Applications can put this in their root source file:
pub const io_mode = .evented;
This will populate std.io.mode
to be std.io.Mode.evented
.
When I/O mode is evented, std.os.read
handles EAGAIN by suspending until the
file descriptor becomes available for reading. Although the std lib
event loop supports epoll, kqueue, and Windows I/O Completion Ports,
this integration with std.os.read
currently only works on Linux.
This integration is currently only hooked up to std.os.read
, and not,
for example, std.os.write
, child processes, and timers. The fact that
we can do this and still have a working master branch is thanks to Zig's
lazy analysis, comptime, and inferred async. We can continue to make
incremental progress on async std lib features, enabling more and more
test cases and coverage.
In addition to std.io.mode
there is std.io.is_async
which is equal to std.io.mode == .evented
. In case I/O mode is async,
std.io.InStream
notices this and the read function pointer becomes an async function
pointer rather than a blocking function pointer. Even in this case,
std.io.InStream
can still be used as a blocking input stream.
Users of the API control whether it is blocking or async at runtime by whether
or not the read function suspends. In case of file descriptors, for
example, this might correspond to whether it was opened with O_NONBLOCK
.
The noasync
keyword makes a function call or await
assert
that no suspension happens. This assertion has runtime safety enabled.
std.io.InStream
, in the case of async I/O, uses by default a 1 MiB
frame size for calling the read function. If this is too large or too
small, the application can globally increase the frame size used by
declaring pub const stack_size_std_io_InStream = 1234;
in their root
source file. This way, std.io.InStream
will only be generated once,
avoiding bloat, and as long as this number is configured to be high
enough, everything works fine. Zig has runtime safety to detect when
@asyncCall is given too small of a buffer for the frame size.
This merge introduces -fstack-report which can help identify large async function frame sizes and explain what is making them so big.
-fstack-report outputs JSON format, which can then be viewed in a GUI that represents the tree structure. As an example, Firefox does a decent job of this.
One feature that is currently missing is detecting that the call stack upper bound is greater than the default for a given target, and passing this upper bound to the linker. As an example, if Zig detects that 20 MiB stack upper bound is needed - which would be quite reasonable - currently on Linux the application would only be given the default of 16 MiB.
There is so much to go over with this feature, and these release notes are already ridiculously long. I'm going to have to resort to listing out some things here, and rely on a future post to elaborate on these features.
@frameSize() usize
@frame() *@Frame(func)
@Frame(func: var) type
It is confirmed that async functions will solve safe recursion in Zig.
Zig's SIMD support in 0.5.0 is still far from complete, but significant progress has been made.
Shawn Landden has a branch of Zig with SIMD fairly complete, and has been maintaining this patchset, as I slowly upstream the commits one-by-one (with adjustments, fixups, etc). Shawn is giving a talk on his work at the October LLVM Dev Meeting: Using LLVM's portable SIMD with Zig
bool
.bool
.comptime
implementation.See #903 for more details.
In Zig 0.4.0 there was this ugly kludge in the C++ stage1 compiler:
// TODO If we have no type_entry for the field, we've already failed to
// compile the program correctly. This stage1 compiler needs a deeper
// reworking to make this correct, or we can ignore the problem
// and make sure it is fixed in stage2. This workaround is for when
// there is a false positive of a dependency loop, of alignment depending
// on itself. When this false positive happens we assume a pointer-aligned
// field, which is usually fine but could be incorrectly over-aligned or
// even under-aligned. See https://github.com/ziglang/zig/issues/1512
} else if (field->type_entry == nullptr) {
this_field_align = g->builtin_types.entry_usize->abi_align;
It was a mistake to ever let this kludge into the C++ stage1 compiler, and it was difficult to remove this kludge in 0.5.0. But it's gone now.
In Zig 0.5.0, the C++ stage1 compiler has the concept of "Lazy Values". This solved the problem
of false positive dependencies without a kludge such as this. It also enabled Zig programs
to explicitly specify struct
field alignment:
field_align.zig
const std = @import("std");
const expect = std.testing.expect;
const Node = struct {
next: *Node,
massive_byte: u8 align(64),
};
test "struct field explicit alignment" {
var node: Node = undefined;
node.massive_byte = 100;
expect(node.massive_byte == 100);
comptime expect(@typeOf(&node.massive_byte) == *align(64) u8);
expect(@ptrToInt(&node.massive_byte) % 64 == 0);
}
$ zig test field_align.zig
1/1 test "struct field explicit alignment"...OK
All tests passed.
In addition to this, "Lazy Values" solved the following bugs:
"Lazy Values" also paved the way for Standard Library integrations with Async Functions.
@Type(comptime info: @import("builtin").TypeInfo) type
This function is the inverse of @typeInfo. It reifies type information
into a type
.
It is available for the following types:
type
noreturn
void
bool
65535
.comptime_int
comptime_float
@typeOf(undefined)
@typeOf(null)
For these types it is a TODO in the compiler to implement:
For these types, @Type
is not available.
There is an open proposal to allow unions and structs.
Thank you to Jonathan Marler for the original implementation of @Type.
Variable declarations can now be called as methods:
var_decl_methods.zig
const std = @import("std");
const expect = std.testing.expect;
const Foo = struct {
a: u64 = 10,
fn one(self: Foo) u64 {
return self.a + 1;
}
const two = __two;
fn __two(self: Foo) u64 {
return self.a + 2;
}
const three = __three;
const four = custom(Foo, 4);
};
fn __three(self: Foo) u64 {
return self.a + 3;
}
fn custom(comptime T: type, comptime num: u64) fn (T) u64 {
return struct {
fn function(self: T) u64 {
return self.a + num;
}
}.function;
}
test "fn delegation" {
const foo = Foo{};
expect(foo.one() == 11);
expect(foo.two() == 12);
expect(foo.three() == 13);
expect(foo.four() == 14);
}
$ zig test var_decl_methods.zig
1/1 test "fn delegation"...OK
All tests passed.
Thank you to Michael Dusan for proposing and implementing this. #3306
std.heap.DirectAllocator
to have more
consistent behavior on Windows and POSIX. It no longer needs to be initialized with state;
users of the API can now refer directly to a global (thread-safe) instance with
std.heap.direct_allocator
. On Windows, rather than being backed by
HeapAlloc
, it is backed by VirtualAlloc
.
std.os.msghdr
definition.dl_phdr_info
std.io.COutStream
gains basic Windows support.std.heap.DirectAllocator
.
Previously the memory would be copied to a different aligned address in some cases where the old offset could have been used. This fixes it so that it will always try to use the old offset when possible, and only uses a different offset if the old one is truly invalid (not aligned or not enough space to store the alloc at the old offset).
std.heap.LoggingAllocator
.std.heap.DirectAllocator
.std.os.linux.sendmmsg
.std.ArrayList.orderedRemove
.std.PackedIntArray
and std.PackedIntSlice
.isize
field.
std.os.time.sleep
and
std.os.time.posixSleep
. Values that do not fit in the native system APIs
will cause a sleep for the longest possible duration and then be handled
as a spurious wakeup.
dl_iterate_phdr
.ARCH_SET_*
definitions for x86_64.io_uring
.std.os.close
now uses close$NOCANCEL
on Darwin,
so that it is impossible to fail.std.os.mprotect
syscall.MAP_LOCKED
flag from load_dynamic_library and enabled the now passing tests.std.meta
and std.meta.trait
"definition" renamed to "declaration", to be inline with the newly clarified semantics of
@hasDecl and @hasField.Deserializer.alignToByte()
and added Test Coverage.windows.unexpectedError
print a human friendly string and
fixed Windows API function prototypes.bcmp
implementation to zig's multi-target libc implementation.
This is especially useful because LLVM 9 now emits calls to bcmp.std.testing.expectEqual
for structs.std.LinkedList
to std.TailQueue
.std.SinglyLinkedList
, and improved
std.heap.ArenaAllocator
to use a singly linked list instead of double.std.http.Headers
.const
on std.process.argsAlloc
.std.crypto
.std.mem.concat
.error.FileBusy
to DeleteFileW
's error set.error.FileNotFound
for PATH_NOT_FOUND
in
DeleteFileW
's error set.@breakpoint()
in there.!u8
from main()
.std.os.abort
no longer calls msvcrt abort() when linking libc. #2071std.os.windows.subsystem
.AT_
constants because they are the same across
architectures. He fixed MAP_
definitions to match the kernel. #2837INT_MAX
. The linux syscall treats this
argument as having type int, so passing extremely long buffer sizes would be misinterpreted
by the kernel. Since "short reads" are always acceptable, just cap it down.std.fs.updateFile
.std.fs.File.updateTimes
.std.os.Stat
structs gain methods to abstract over the platform differences
with regards to mtime
, ctime
, atime
.std.unicode.utf8ToUtf16Le
. On a simple test input:/dev/urandom
being a character device.
This is a security measure. std.os.getrandom
will return
error.NoDevice
if /dev/urandom
is not a character device.std.rb.Tree.lookup
and added
Test Coverage.std.elf.Elf
open functions to return Elf
struct directly instead of filling in pointer. #2998std.os.getrandom
on FreeBSD use the
getrandom
libc function. #2993std.os.windows.advapi32.RtlGenRandom
. #3015std.math.min
on integer operands will now return
the type of the smaller integer when possible.std.c.printf
.std.BloomFilter
._start
function
when the Root Source File
already has one. This enables Zig applications to export their own startup if they wish to
take this level of responsibility.std.heap.FixedBufferAllocator.reset
.std.os.gethostname
.std.ascii.allocLowerString
.std.ascii.eqlIgnoreCase
.std.ascii.indexOfIgnoreCasePos
.std.ascii.indexOfIgnoreCase
.Previously, due to a bug, the stack trace iteration code was using the number of frames collected as the number of frames to print, not recognizing the fixed size of the buffer. So it would redundantly print items, matching the total number of frames ever collected. Now the iteration code is limited to the actual stack trace frame count, and will not print duplicate frames. #2447 #2151
LemonBoy implemented a bunch of fixes for the DWARF parser (#2254):
DW_TAG_lexical_block
for variable declarations.void
a signed type according to DWARF. This follows the
convention set by C so that lldb stops complaining about it.Thanks to this, and some other debug info fixes from LemonBoy, stack traces now work in release builds:
test.zig
const std = @import("std");
fn foo() !void {
return error.TheSkyIsFalling;
}
pub fn main() !void {
try foo();
}
$ zig build-exe test.zig --release-safe
$ ./test
error: TheSkyIsFalling
/home/andy/dev/www.ziglang.org/docgen_tmp/test.zig:4:5: 0x20de63 in std.special.posixCallMainAndExit (test)
return error.TheSkyIsFalling;
^
/home/andy/dev/www.ziglang.org/docgen_tmp/test.zig:8:5: 0x20de6b in std.special.posixCallMainAndExit (test)
try foo();
^
LemonBoy additionally implemented reading symbol names from DWARF sections, so stack
traces on Linux now have actual function names instead of ???
's. You can see
this in the above stack trace.
There is an open issue with this, however - scanning all the symbol names is a bit slow. A future improvement will improve the "Big-O" performance of this function, and bring Zig stack traces up to the same speed that one gets with, for example, gdb.
Despite this trouble for debug info of very large Linux binaries, most projects will experience faster
stack traces thanks to Marc Tiehuis switching to using mmap
to read the debug info.
Usually, such techniques are frowned upon due to making error handling difficult, however, this
debug info code is currently designed for the use case of dumping a stack trace when
the application has already panicked, and is about to abort. And so, an error in loading debug
info from disk would be handled by aborting anyway.
In addition, the following improvements were made:
Marc Tiehuis created a plan to overhaul the std.fmt API. The plan is partially implemented in 0.5.0, but there is remaining work.
The main difference in 0.5.0 is the positional, precision, and width support. This removes the odd width and precision specifiers found and replacing them with the more consistent api described in #1358.
Take the following example:
{1:5.9}
This refers to the second argument (0-indexed) in the argument list. It will be printed with a minimum width of 5 and will have a precision of 9 (if applicable).
Not all types correctly use these parameters just yet. There are still some missing gaps to fill in. Fill characters and alignment have yet to be implemented.
In addition:
std.fmt.format
to avoid infinite
recursion from self-references.std.fmt
to handle std.SegmentedList
:{*:5}
FormatOptions
arguments non-comptime
.
No need and should help avoid exessive function monomorphizaton.Marc Tiehuis ported upstream changes from musl's math functions.
This also starts the documentation effort for the math/ subdirectory. The intent is to use this as a somewhat representative test-case for any work on the documentation generator.
std.math.big.Rational
.std.math.big.Int.toString
, as well as
handling zero-limb trailing div case and fixing divN/gcdLehmer and fuzz-test failures.std.math.big.Int
sign and length fields,
taking it down from 40 to 32 bits on a 64-bit architecture.math.nan
usage in cos.std.fmt.parseFloat
.std.math.ceilPowerOfTwo
and
std.math.ceilPowerOfTwoPromote
. #2426comptime_int
in
std.math.shl
and std.math.shr
.std.math.isPowerOfTwo
, and
Ryan Liptak updated the standard library to take advantage of it.Thanks to LemonBoy, Zig now has support for threadlocal
variables on
Linux without relying on libc, on the following architectures:
In addition to wider architecture support, he implemented on-demand TLS allocation. Previously, if there were too many thread local variables, Zig would panic on startup.
With these changes, Zig has proper support for TLS on Linux.
In Zig 0.5.0, OS abstractions are organized in a straightforward manner.
std.os
is "Zig-flavored POSIX". All the "bits" familiar to C
programmers are available in this namespace, such as O_RDONLY
and
open
. The functions have errno translated to Zig errors (on Linux
without libc, there is no thread local variable
for errno
🎉) and slices are used rather than raw pointers where appropriate.
Higher level, cross-platform abstractions are available in category-specific namespaces,
for example std.fs.File.openRead
.
std.os.windows
has "Zig-flavored Windows", with
GetLastError
translated to Zig errors. Raw Windows APIs are available
directly via namespaces named after their DLLs, for example
std.os.windows.kernel32.ExitProcess
.
Zig's optional integration with libc is significantly more robust. std.os
functions call libc functions when linking against libc, and otherwise use the
operating system's syscall ABI directly.
After some experimentation, it was concluded that Windows does not integrate well with libc, and so on Windows, even when linking libc, the native Windows API calls are used rather than libc API.
See #2380 for more details and discussion.
Zig's self-hosted parser is in the standard library - std.zig.parse
.
It's the backbone of zig fmt.
I've said before that recursion is one of the enemies of perfect software, because it represents a way that a program can fail with no foolproof way of preventing it. With recursion, pick any stack size and I'll give you an input that will crash your program. Embedded developers are all too familiar with this problem.
It's always possible to rewrite code using an explicit stack using heap allocations, and that's exactly what Jimmi did in the self-hosted parser.
This implementation of the self-hosted parser is an interesting case study of avoiding recursion by using an explicit stack. It is essentially a hand-written recursive descent parser, but with heap allocations instead of recursion. This code is truly a work of art. I like to call it "Jimmi's non-recursive recursive-descent parser".
When Jimmi originally implemented the code, we thought that we could not solve the unbounded stack growth problem of recursion. However, now we have a plan for safe recursion.
And so it was time to lay the code to rest. This is where Stevie Hryciw came in. Stevie rewrote the entire self-hosted parser, to the full grammar specification. This was a large project spanning across several weeks. During this time, Stevie endured painful rebases and dutifully updated the pull request description to keep everyone informed.
Stevie didn't stop there - he followed up by analyzing Performance Impact as well as Readability Impact. He writes:
Here are some informal tests of parser_test.zig with perf stat -d on x86_64 Linux.
In the absence of visualizations and more formal testing, some quick findings based on my system:
Indentation stats of std/zig/parse.zig
:
master | stage2-recursive-parser --------------------------------+--------------------------------- indent count | indent count ------------ | ------------ 0 92 | 0 263 1 241 | 1 803 2 123 | 2 763 3 291 | 3 334 4 654 | 4 133 5 662 | 5 60 6 700 | 6 28 7 347 | 7 5 8 229 | 9 42 | 10 18 | avg indentation level: 4.796999 | avg indentation level: 1.827543 source lines of code: 3399 | source lines of code: 2389
x86_64-linux and Windows now have, by default, a segfault handler that is attached before main()
.
Thanks to this, Zig programs now have stack traces for segfaults:
test.zig
pub fn main() void {
dereferenceAPointer(@intToPtr(*i32, 0x1));
}
fn dereferenceAPointer(ptr: *i32) void {
ptr.* = 10;
}
$ zig build-exe test.zig
$ ./test
Segmentation fault at address 0x1
/home/andy/dev/www.ziglang.org/docgen_tmp/test.zig:6:13: 0x2281bd in dereferenceAPointer (test)
ptr.* = 10;
^
/home/andy/dev/www.ziglang.org/docgen_tmp/test.zig:2:24: 0x2280fd in main (test)
dereferenceAPointer(@intToPtr(*i32, 0x1));
^
/home/andy/Downloads/zig/lib/std/special/start.zig:194:22: 0x22718b in std.special.posixCallMainAndExit (test)
root.main();
^
/home/andy/Downloads/zig/lib/std/special/start.zig:102:5: 0x22706f in std.special._start (test)
@noInlineCall(posixCallMainAndExit);
^
(process terminated by signal)
This can be disabled:
test.zig
pub const enable_segfault_handler = false;
pub fn main() void {
dereferenceAPointer(@intToPtr(*i32, 0x1));
}
fn dereferenceAPointer(ptr: *i32) void {
ptr.* = 10;
}
$ zig build-exe test.zig
$ ./test
(process terminated by signal)
This works because the standard library uses the new feature @import("root") to check for this opt-out.
Thank you to Rocknest for a proof-of-concept implementation of this in #2355.
std.HashMap
indexing by avoiding modulo operator.
x % y
can be optimized if y is a power of two by doing
x & (y-1)
instead. HashMap already enforces power of two capacity,
so we can take advantage of this optimization.std.HashMap
, and
made it round up to the nearest power of two. It now optimizes for the expected count.
Finally, he added putAssumeCapacity
.std.HashMap
APIs that assert the common case:putNoClobber()
for put()
removeAssertDiscard()
for remove()
std.HashMap.getValue()
.std.AutoHashMap
with an enum
key. #2669autoHash
make use of hashing streaming interface.
Notably, Sahnvour made std.HashMap
consistent about whether or not it will
dereference keys. std.AutoHashMap
no longer will dereference slices
([]const u8
or []u8
), or any pointer type
for that matter:
test.zig
const std = @import("std");
test "AutoHashMap with slices" {
var map = std.AutoHashMap([]const u8, bool).init(std.heap.direct_allocator);
}
$ zig test test.zig
/home/andy/Downloads/zig/lib/std/hash/auto_hash.zig:176:9: error: std.auto_hash.autoHash does not allow slices (here []const u8) because the intent is unclear. Consider using std.auto_hash.hash or providing your own hash function instead. Consider std.StringHashMap for hashing the contents of []const u8.
@compileError("std.auto_hash.autoHash does not allow slices (here " ++ @typeName(Key) ++
^
/home/andy/Downloads/zig/lib/std/hash_map.zig:540:21: note: called from here
autoHash(&hasher, key);
^
/home/andy/Downloads/zig/lib/std/hash_map.zig:538:29: note: called from here
fn hash(key: K) u32 {
^
std.StringHashMap
is provided for this use case
instead:
string_hash_map.zig
const std = @import("std");
test "StringHashMap" {
var map = std.StringHashMap(bool).init(std.heap.direct_allocator);
_ = try map.put("hello", true);
}
$ zig test string_hash_map.zig
1/1 test "StringHashMap"...OK
All tests passed.
Additionally:
std.HashMap
, and improve autoHash
to handle more types and behave more correctly.std.mem.eql
and simplified
std.hash_map.eqlString
.Marc Tiehuis made a series of improvements to hashing performance:
Inline full slice hashing - this gives moderate speed improvements when hashing small keys. The crc/adler/fnv inlining did not provide enough speed up to warrant the change.
Old:
wyhash
small keys: 2277 MiB/s [c14617a1e3800000]
siphash(1,3)
small keys: 937 MiB/s [b2919222ed400000]
siphash(2,4)
small keys: 722 MiB/s [3c3d974cc2800000]
fnv1a
small keys: 1580 MiB/s [70155e1cb7000000]
adler32
small keys: 1898 MiB/s [00013883ef800000]
crc32-slicing-by-8
small keys: 2323 MiB/s [0035bf3dcac00000]
crc32-half-byte-lookup
small keys: 218 MiB/s [0035bf3dcac00000]
New:
wyhash
small keys: 2775 MiB/s [c14617a1e3800000]
siphash(1,3)
small keys: 1086 MiB/s [b2919222ed400000]
siphash(2,4)
small keys: 789 MiB/s [3c3d974cc2800000]
fnv1a
small keys: 1604 MiB/s [70155e1cb7000000]
adler32
small keys: 1856 MiB/s [00013883ef800000]
crc32-slicing-by-8
small keys: 2336 MiB/s [0035bf3dcac00000]
crc32-half-byte-lookup
small keys: 218 MiB/s [0035bf3dcac00000]
Improve siphash performance for small keys by up to 30% (#3124) - this removes the partial buffer handling from the full slice API.
./benchmark --filter siphash --count 1024
Old:
siphash(1,3)
iterative: 3388 MiB/s [67532e53a0d210bf]
small keys: 1258 MiB/s [948c91176a000000]
siphash(2,4)
iterative: 2061 MiB/s [f792d39bff42f819]
small keys: 902 MiB/s [e1ecba6614000000]
New:
siphash(1,3)
iterative: 3410 MiB/s [67532e53a0d210bf]
small keys: 1639 MiB/s [948c91176a000000]
siphash(2,4)
iterative: 2053 MiB/s [f792d39bff42f819]
small keys: 1074 MiB/s [e1ecba6614000000]
Simplify wyhash and improve speed - this removes the exposed stateless variant since the standard variant has similar speed now.
Using ./benchmark --filter wyhash --count 1024
, the speed change has
changed from:
wyhash
iterative: 4093 MiB/s [6f76b0d5db7db34c]
small keys: 3132 MiB/s [28c2f43c70000000]
to
wyhash
iterative: 6515 MiB/s [673e9bb86da93ea4]
small keys: 10487 MiB/s [28c2f43c70000000]
@setGlobalLinkage
section. @setGlobalLinkage
was removed with
#462. The not-yet-implemented proposal for external weak symbols is #1917.-target
command line argument.allowzero
.for else
and break
.for else
example. #2614example/
folder. Code moved to become standalone tests. #2759
zig build
is still in an experimental, proof-of-concept phase, and will remain
that way until at least the package manager is complete.
However, there were still improvements to zig build
this release cycle:
zig build
.zig build
now searches up the directory hierarchy for build.zig
. #2587setLibCFile
APIstandardTargetOptions
and deprecated setTarget
in favor of
setTheTarget
.DESTDIR
environment variable. #2929builder.findProgram
test and fixed references.'/'
showing up in application/library names.linkSystemLibrary
integrates with pkg-config
.artifact.enable_qemu = true;
and
artifact.enable_wine = true;
, respectively.
zig fmt
now fixes invalid whitespace instead of rejecting it.
The // zig fmt: off
directive is ignored for whitespace fixes.
a && b
as a & &b
. Now it has a helpful error message: "`&&` is invalid. Note that `and` is boolean AND.". #2660noasync
.// zig fmt: off/on
.if
.Thanks to WebAssembly Support improvements, there is also a web-based formatter made by Shritesh Bhattarai.
Zig now provides libc for the following targets (find this information with zig targets
):
Zig ships with the source code to musl. Zig 0.5.0 updates to the 1.1.23 release of musl, plus a handful of patches to fix various issues with 64-bit ARM Support and RISC-V Support. All these patches are merged into musl upstream and will be part of the next release.
Previously, the way that Zig built musl from source skipped some necessary files. LemonBoy solved this issue.
Additionally, the updating process for musl was brittle and confusing. Now, the process is streamlined and fully documented. Unnecessary patches were dropped.
Now, Zig has excellent Test Coverage of musl. The Zig test suite tests building musl for these targets:
In Zig 0.5.0, not only can Zig provide dynamically linked glibc for any target, but it can also provide any version of glibc for any target:
getrandom.zig
const std = @import("std");
pub fn main() void {
var buf: [16]u8 = undefined;
_ = std.c.getrandom(&buf, buf.len, 0);
}
$ ./zig build-exe test.zig -lc -target x86_64-linux-gnu -target-glibc 2.24
lld: error: undefined symbol: getrandom
>>> referenced by test.zig:5 (/home/andy/dev/zig/build/test.zig:5)
>>> ./test.o:(main.0)
$ ./zig build-exe test.zig -lc -target x86_64-linux-gnu -target-glibc 2.25
$ ./test
$
Updating to the newest glibc version is now streamlined and fully documented. Even though Zig now supports every version of glibc, the amount of bytes required for a Zig installation with regards to glibc has decreased, because a dummy libc file is no longer required, as it is generated on-the-fly depending on the target version selected.
The target glibc version is exposed in @import("builtin")
and is used by
the Standard Library to do a glibc version check, to decide whether to use libc
getrandom
or read from /dev/urandom
. #397
The supported glibc version range is increased to include 2.30.
Building glibc now has Test Coverage when the -Denable-qemu
and
-Denable-foreign-glibc
options are enabled, for these targets:
Zig now ships with the source code and header files to mingw-w64 (version 6.0.0), and uses this to provide libc when targeting Windows.
Combining this with Wine, one can cross-compile C code for Windows and run it, without even touching a Windows computer:
hello_windows.c
#include <stdio.h>
int main(int argc, char **argv) {
printf("Hello Windows\n");
return 0;
}
$ zig build-exe --c-source hello_windows.c -lc -target x86_64-windows-gnu
$ wine hello_windows.exe
Hello Windows
Building and linking against mingw-w64 libc now has Test Coverage
for the x86_64-windows-gnu
target.
One of the use cases for this is creating Zig packages out of C libraries, and there is now a proof of concept of this with SDL2.
The open-source game Legend of Swarkland is being written in Zig, and it uses this SDL2 package in order to support cross compiling for Windows with nothing installed other than Zig. The developer, Josh, does not have a Windows computer to test on, but has a brother who uses Windows he wants to be able to playtest his game. Using only Zig and Wine, Josh can create Windows builds of his game and even test them, before sending his brother an executable.
$ git clone https://github.com/thejoshwolfe/legend-of-swarkland --recursive
$ cd legend-of-swarkland
$ zig build -Dtarget x86_64-windows-gnu
$ ls zig-cache/bin
legend-of-swarkland.exe legend-of-swarkland_headless.pdb
legend-of-swarkland_headless.exe legend-of-swarkland.pdb
Integration with mingw-w64 is easy and clean, because their headers are already
multi-architecture. Thank you especially to IRC user wbs
, who is
largely responsible for that, and for patiently helping me
work through my own issues caused by hacking up the mingw-w64 build system to
integrate into Zig.
Zig provides libc even when compiling in freestanding mode. This enables some C libraries to work even when there is no host Operating System.
In Zig 0.5.0, this concept is a little bit more fleshed out and clear. One can observe this freestanding libc in action when building C code for WebAssembly.
There is still a lot to do on this front, and it could be an engaging project for contributors.
LemonBoy made several improvements:
Thanks to C pointers supporting optional syntax,
NULL
pointers now translate to null
.
Additionally:
In Zig 0.4.0, the translate-c
and @cImport implementations are 5,000 lines of C++.
However, in this release, Zig is transitioning to a fully self-hosted implementation.
The parts of translate_c.cpp
that interact with the Clang C++ API have been extracted
into zig_clang.h
and zig_clang.cpp
. This is a C API on top of the C++ API
with some careful static assertions to ensure the file is kept up-to-date as Clang's C++ API changes.
translate_c.cpp
now interacts with the Clang C++ API exclusively via zig_clang.h
.
These files are generally useful for any project and they are MIT licensed.
Based on zig_clang.h
, clang.zig
is created, updated, and maintained. This is
extern functions and types so that Zig code can utilize the C layer on top of the Clang C++ API.
And with this, we have src-self-hosted/translate_c.zig
which is the self-hosted implementation
of translate-c
(and @cImport). This is exposed with zig translate-c-2
.
Until the self-hosted implementation is brought up to feature parity, zig translate-c
and @cImport are still the C++ implementation. This work is partially done thanks to Stevie Hryciw; you can get a sense of progress by
examining the test cases.
More contributions welcome!
See #1964 for more details.
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 gcc 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.
__muloti4
.
LemonBoy also fixed an edge case in addXf3
- since the operands may be zero,
he used the wrapping operators to avoid a spurious integer-overflow error. He then proceeded
to other fixes:
__aeabi_{f,d}cmp*
.__extendhfsf2
using @bitCast.With Zig 0.5.0, compiler-rt much more complete, but not fully. There are some missing functions, and it's planned to do an audit before 1.0.
Zig now has much more exhaustive test coverage of foreign architectures, thanks to QEMU. These new options are available to zig build when running the Zig test suite:
-Denable-qemu
- runs cross-compiled compiler-rt, behavior, and std lib tests on
foreign architectures.-Denable-wine
- runs cross-compiled compiler-rt, behavior, and std lib tests
with Wine.-Denable-foreign-glibc=path
- if you have builds of glibc for other architectures
available, this enables testing cross-compiled compiler-rt, behavior, and std lib tests
on foreign architectures, dynamically linking glibc.-Dskip-non-native
- skips all non-native tests.-Dskip-libc
- if you don't want to wait for e.g. musl to build.See CONTRIBUTING.md for more details.
Michael Dusan implemented a new kind of test coverage for stack traces, to catch future regressions in Debug Info and Stack Traces.
Additionally:
artifact.enable_qemu = true;
and
artifact.enable_wine = true;
, respectively.std.debug.global_allocator
is deprecated as far as being used in tests
is concerned. Tests should use std.heap.FixedBufferAllocator
and stack memory
instead.
0
for comptime
types.
This defines @sizeOf to be the runtime size of a type, which means
that it is zero for types such as comptime_int, type, and (enum
literal). #2209
#!
) support. #2165test "<name>"
in order
to be more easily distinguished from functions with similar names. #2267
--enable-pic
and --disable-pic
to -fPIC
and
-fno-PIC
.undefined
, Zig notices this
case and does a single
Valgrind client request rather than
N. Speeds up all allocators in safe modes. #2388
--bundle-compiler-rt
linker option.zig cc
rather than concatenating
all assembly blocks into one large string and handing that to LLVM. This fixed bugs as well as
enabling C preprocessor support for assembly files.@import("builtin")
.std/special/bootstrap.zig
is renamed to std/special/start.zig
.
This makes a bit more sense, and actually is kinda important to make sense because it shows
up in stack traces. Further, the logic on whether to include this file is improved.--override-lib-dir
.lib/
directory that mirrors the directory tree of installed files,
and std/
is moved into it appropriately. These changes led to
Self-Hosted Installation of Library Files.AstNode
objects have a src()
method
to print the corresponding source file, line number, and column number. This is handy
when using a debugger.-l
parameters as an alias for --library
. As
an example, one may specify -lc
rather than --library c
./debug:fastlink
when building with MSVC and debug info.zig_panic
rather than
having LLVM abort.--test-cmd
is provided to zig test
, it will run it regardlesso
of whether the binary is native. This enables, for example, testing with
QEMU.-D
CLI parameter for setting C preprocessor definitions.When Zig compiles C code (using libclang), it automatically enables .d file generation so that Zig can learn the dependencies and do proper caching.
Previously, Zig's .d file parser was written in C++ and a bit brittle. Michael Dusan dove head-first into this and implemented a robust .d file parser, in self-hosted Zig code, complete with unit tests.
Unfortunately, there is still an open issue regarding this, because the first line Clang outputs cannot be parsed unambiguously. Clang Bug Report. If you, the reader of these release notes, are a Clang developer, please fix 🙏
@import("builtin")
gained strip_debug_info
which is
a comptime bool
value telling whether --strip
was passed to the
compiler.
This makes Zig code aware at compile-time of when it will not have any debug information available at runtime. The standard library now takes advantage of this to avoid Zig binaries containing useless debug info code.
This, along with Timon Kruiper's contribution of enabling the equivalent of
-ffunction-sections
in Zig's LLVM codegen, resulted in tiny
ReleaseSmall binaries:
hello.zig
const std = @import("std");
pub fn main() void {
std.debug.warn("Hello, World!\n");
}
$ zig build-exe hello.zig --release-small --strip --single-threaded
$ ./hello
Hello, World!
$ ls -ahl ./hello
-rwxr-xr-x 1 andy users 10K Sep 26 15:56 ./hello
$ ldd ./hello
not a dynamic executable
The Windows build is even smaller:
$ zig build-exe hello.zig --release-small --strip --single-threaded -target x86_64-windows
$ wine64 ./hello.exe
Hello, World!
$ ls -ahl ./hello.exe
-rwxr-xr-x 1 andy users 3.0K Sep 26 15:57 ./hello.exe
Zig's ability to create tiny executables is especially attractive for WebAssembly.
Previously, when editing source files, it was required to make install
(msbuild -p:Configuration=Release INSTALL.vcxproj
on Windows) in order
to test changes. This used cmake's install()
function for all the library files,
such as the Standard Library, as well as the libc files that Zig
ships with. Unfortunately, this printed something like this every time:
-- Installing: /home/andy/dev/zig/build/lib/zig/std/array_list.zig
-- Installing: /home/andy/dev/zig/build/lib/zig/std/ascii.zig
-- Installing: /home/andy/dev/zig/build/lib/zig/std/atomic/int.zig
(...snip...)
...for all 6,091 lib files. Even when the files are already installed, it would print:
-- Up-to-date: /home/andy/dev/zig/build/lib/zig/std/array_list.zig
-- Up-to-date: /home/andy/dev/zig/build/lib/zig/std/ascii.zig
-- Up-to-date: /home/andy/dev/zig/build/lib/zig/std/atomic/int.zig
(...snip...)
There is no way to disable this in cmake. This caused make install
on my Linux computer to take 2.4 seconds even when it has to do nothing, and prints all these
unnecessary lines to stdout. On my Windows it took even longer, upwards of 5 seconds.
Now, installation of lib files is self-hosted, using zig build. Running
make
when there is nothing to do takes 0.3 seconds on my Linux computer;
2.4 on Windows.
Unfortunately, lib file installation
happens in the make
target instead of the make install
target, because
cmake has no way to add a custom command to the install target. So that's why Zig now has the
option -DZIG_SKIP_INSTALL_LIB_FILES=ON
, which is recommended to enable for
contributors to Zig. It's off by default because otherwise installing Zig would be missing
library files. However when contributing to Zig, running the zig
binary from
the build directory will search upwards for library files and find them directly in the source tree.
This means one can directly edit the Standard Library in the source tree, and changes
will be picked up without needing to run make
at all.
Warning: Unable to write cache file [..]: unexpected seek failureBen took care to make os_file_close poison the file handle after closing, to help track down future use-after-close bugs.
-fvisibility-inlines-hidden
to the build flags.
On macOS building with Xcode/clang the linker complains loudly when
symbol visibility is inconsistent. This option syncs visibilty setting
of both LLVM and Zig.
std.io.InStream
.clock_gettime
on Linux systems without VDSO.std.heap.FailingAllocator
not counting allocations.K_DARWIN
as expected on the archive file. This allowed removing macOS-specific linking hacks.?*void
const casts. #2578undefined
used as a type. #2436if
, for
,
and while
.extern
variables. Additionally fixed @export not respecting the name parameter. Previously, the variable
name would be used instead. #2679zig run
.std.atomic.Queue.isEmpty
.std.math.absFloat
and added tests.std.net.Address.family
.switch
with null
and T peer types and inferred result location type. #2762for
with null
and T peer types and inferred result location type. #2762add_source_file()
.std.rb.Node.getParent
to return optional, fixing #2962.zig cc
,
resolves intrinsics issues. #3027build-obj
not working with C files that use libc. #3093**
causing a segfault when run. #3095void
array as a local variable initializer. #1767std.os.getrandom
not filling the entire buffer when
requesting larger than a 32-bit integer. #3012One interesting thing here is that if the compiler was written in Zig this bug likely never would have happened as Zig would protect you from dereferencing a NULL pointer like this. The tag_type field would either be an optional type which would require testing for NULL before using it, or it wouldn't be optional and couldn't be set or initialized to NULL.
fatal: not a git repository
message.std.fmt.parseFloat
not parsing
correctly.RLIMIT_NOFILE
. Patch lifted from node.js commit
6820054d2d42ff9274ea0755bea59cfc4f26f353.
Thanks to Ben Noordhuis for the suggestion and patch.void
argument. #2612AT_FDCWD
definition.tail
attribute for @panic causing undefined behavior in the stage1
C++ compiler when it hit asserts, causing it to print garbage memory to the terminal. #3262packed struct
.zig cc
.output-dir
if it does not exist.
#2637usingnamespace
causing error for redeclaration for the same var
node. #3316mmap2
offsets if not multiple of page size.Zig has known bugs.
Zig is immature. Even with Zig 0.5.0, working on a non-trivial project using Zig will likely mean participating in the development process.
The first release that will ship with no known bugs will be 1.0.0.
The major theme of next release cycle will be safety.
Along with this, it's planned to resume work on the self-hosted compiler now that new Async Functions are done.
Issues that have the possibility of breaking changes to the language will be prioritized, so that the language can be stabilized.
Having a package manager built into the Zig compiler is a long-anticipated feature. Zig 0.5.0 does not have this feature, however the Zig project now that Async Functions are complete, it's time to begin on networking in the Standard Library. I expect to complete this along with at least an early prototype of the package manager during the next release cycle.
Here are proposals that have been accepted during the 0.5.0 release cycle, to give you an idea of the upcoming changes to Zig:
During this release cycle, I joined the GitHub Sponsors program. This transition went fairly smoothly, thanks to Devon Zuegel. It's pretty clear to me that she's going above and beyond what's professionally required of her to help maintainers get sponsored.
At this point, funding for the Zig project is stable. There are enough funds that I can continue to work full time on Zig without burning down my savings. Based on current trends, I should even be able to get health insurance soon.
However, community growth has outpaced funding growth. My job has become more and more demanding over time. Even just merging pull requests at this point is a full time job. I have averaged merging 1.5 pull requests per day into Zig for the last 4 years. There is more than enough work for 2 full-time developers on Zig, and I would love to get to the point where paying another full-time developer is possible.
To facilitate this, I'm planning on starting Zig Software Foundation non-profit organization. I am looking for recommendations for a lawyer who would help me set this up. If you know one, please send me an email.
Special thanks to those who sponsor Zig. Because of you, Zig is not driven by the needs of a business; instead it exists solely to serve the open source community.