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 even if they dislike it.
This release features 6 months of work and changes from 46 different contributors, spread among 875 commits.
Special thanks to my sponsors who provide financial support. You're making Zig sustainable.
This release of Zig upgrades to LLVM 8. Zig operates in lockstep with LLVM; Zig 0.4.0 is not compatible with LLVM 7.
Notably this means that Zig now has WebAssembly Support, as well as recognizing the HermitCore and Hurd operating systems. On the other hand, support for the Nios II architecture is dropped, due to it being an experimental target which was largely unmaintained.
Marc Tiehuis started an initial FreeBSD branch. Greg V picked up the figurative baton in #1661, and Marcio Giaxa completed the effort.
Now, FreeBSD works in the stage1 C++ compiler code, as well as the Zig Standard Library, which means it now has Tier 2 Support. To achieve Tier 1 Support, these things are needed:
Despite these issues, thanks to SourceHut, FreeBSD does have some CI testing support, as well as automated binary builds available for x86_64 on the download page.
See #1759 for more details.
Maya Rashish ported the stage1 C compiler as well as the Zig Standard Library to be compatible with NetBSD. Have a look at their writeup: Porting Zig to NetBSD - a fun, speedy port
NetBSD is limited to Tier 2 Support due to lack of a continuous integration service, as well as a missing implementation of building the system's libc startup files lazily from source.
Now that Zig uses LLVM 8, the wasm32
target architecture is
guaranteed to be available in Zig.
Ben Noordhuis fixed zig build-exe
for the wasm32
target
(#1570).
Shritesh Bhattarai added --allow-undefined
and --export-all
to the wasm linker line, solving #1622.
There may be a better way to catch link errors in the future, but for now, this makes
WebAssembly work out-of-the-box in Zig:
extern fn print(i32) void;
export fn add(a: i32, b: i32) void {
print(a + b);
}
$ zig build-exe math.zig -target wasm32-freestanding
test.js
const fs = require('fs');
const source = fs.readFileSync("./math");
const typedArray = new Uint8Array(source);
WebAssembly.instantiate(typedArray, {
env: {
print: (result) => { console.log(`The result is ${result}`); }
}
}).then(result => {
const add = result.instance.exports.add;
add(1, 2);
});
$ node test.js
The result is 3
There is also the "WebAssembly System Interface" operating system target, wasi
.
I haven't really explored this use case yet, but I did make the C integer types work for it.
More research and exploration is needed.
Shawn Landden implemented arm64 Linux support in the Zig Standard Library, as well as the necessary stage1 C++ compiler changes.
The arm64-linux target has now graduated to Tier 2 Support. To achieve Tier 1 Support, we would need some way to run Continuous Integration tests on arm64 linux.
SourceHut has experimental arm64 Debian images, which may be worth looking into.
GitHub user nebulaeonline approached the Zig project with an interesting use case: creating a UEFI application.
nebulaeonline submitted a pull request adding UEFI as a new operating system that Zig recognizes. The patch contained the linker configuration and a few updates to the Zig Standard Library.
With this patch merged, nebulaeonline went on to work on their UEFI barebones project. It looks like they ended up using C rather than Zig in the end, most likely due to Zig's lack of maturity. Here I have linked to the last revision before they removed the Zig code, in case anyone wants to poke around. Since then Zig has gained C Pointers which was a large pain point for nebulaeonline.
Zig now uses a "support tier" system to communicate the level of support for different targets.
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.One of the main features of Zig is @cImport and C Translation. This is accomplished by linking against Clang libraries. Clang is a fully featured C and C++ compiler. Zig has all this functionality - so we may as well expose it!
The zig cc
command exposes clang, which you can see with this amusing output:
$ zig cc --version
clang version 8.0.0
...
zig cc
is a bit of a low level command. The higher level command line
interface --c-source
(or the corresponding Zig Build System API)
is recommended instead, for a few reasons:
zig cc
represents a fresh build of clang, which does not have such patches.
This is actually A Good Thing - more on this below.
--c-source
:-march=native
is enabled for the native target.-target
, --color
, and
--strip
will apply to both C and Zig Code.Here's an example of Zig building some C code:
hello.c
#include <stdio.h>
int main(int argc, char **argv) {
printf("Hello world\n");
return 0;
}
$ zig build-exe --c-source hello.c --library c
$ ./hello
Hello world
You can use --verbose-cc
to see what C compiler command this executed:
$ zig build-exe --c-source hello.c --library c --verbose-cc
zig cc -MD -MV -MF zig-cache/tmp/42zL6fBH8fSo-hello.o.d -nostdinc -fno-spell-checking -isystem /home/andy/dev/zig/build/lib/zig/include -isystem /home/andy/dev/zig/build/lib/zig/libc/include/x86_64-linux-gnu -isystem /home/andy/dev/zig/build/lib/zig/libc/include/generic-glibc -isystem /home/andy/dev/zig/build/lib/zig/libc/include/x86_64-linux-any -isystem /home/andy/dev/zig/build/lib/zig/libc/include/any-linux-any -march=native -g -fstack-protector-strong --param ssp-buffer-size=4 -fno-omit-frame-pointer -o zig-cache/tmp/42zL6fBH8fSo-hello.o -c hello.c -fPIC
Note that if I run the command again, there is no output, and it finishes instantly:
$ time zig build-exe --c-source hello.c --library c --verbose-cc
real 0m0.027s
user 0m0.018s
sys 0m0.009s
This is thanks to Build Artifact Caching. Zig automatically parses the .d file that clang produces and uses a robust caching system to avoid duplicating work.
In the Zig Build System, builder.addCExecutable
used to be a system C compiler driver. That functionality is deleted. Instead,
build scripts can attach C source files to any executable, library, or object
with foo.addCSourceFile
. You can see a nice example of
this in Michael Dusan's benchmark.unicode project.
Don't forget to use foo.linkSystemLibrary("c");
if the C code
expects to use libc.
You may have noticed that the
Zig-generated C compilation command included -nostdinc
.
This is a crucial step in the direction Zig is headed - providing consistent, reliable builds that are insulated from the system-specific differences in the wild.
This affects @cImport and
C Translation as well. By default, Zig no longer looks in
system paths for C header files, instead relying only on what Zig ships and what
users explicitly request with -isystem
.
However, when one uses the Zig Build System, and uses
linkSystemLibrary
API, Zig takes that as a hint to look in
system default search paths.
Marc Tiehuis proposed to expose this functionality to the command line interface,
and that is likely to be an accepted proposal.
Thanks to Akuli for adding /lib/x86_64-linux-gnu
or similar to default
system library search paths for zig build scripts.
One piece to this puzzle is that Zig now ships with libc. You can find the available
libc targets with zig targets
:
...
Available libcs:
aarch64_be-linux-gnu
aarch64_be-linux-musl
aarch64-linux-gnu
aarch64-linux-musleabi
armeb-linux-gnueabi
armeb-linux-gnueabihf
armeb-linux-musleabi
armeb-linux-musleabihf
arm-linux-gnueabi
arm-linux-gnueabihf
arm-linux-musleabi
arm-linux-musleabihf
i386-linux-gnu
i386-linux-musl
mips64el-linux-gnuabi64
mips64el-linux-gnuabin32
mips64el-linux-musl
mips64-linux-gnuabi64
mips64-linux-gnuabin32
mips64-linux-musl
mipsel-linux-gnu
mipsel-linux-musl
mips-linux-gnu
mips-linux-musl
powerpc64le-linux-gnu
powerpc64le-linux-musl
powerpc64-linux-gnu
powerpc64-linux-musl
powerpc-linux-gnu
powerpc-linux-musl
riscv32-linux-musl
riscv64-linux-gnu
riscv64-linux-musl
s390x-linux-gnu
s390x-linux-musl
sparc-linux-gnu
sparcv9-linux-gnu
x86_64-linux-gnu
x86_64-linux-gnux32
x86_64-linux-musl
What this means is that --library c
for these targets does not depend
on any system files!
Let's look at that C hello world example again:
$ zig build-exe --c-source hello.c --library c
$ ./hello
Hello world
$ ldd ./hello
linux-vdso.so.1 (0x00007ffd03dc9000)
libc.so.6 => /lib/libc.so.6 (0x00007fc4b62be000)
libm.so.6 => /lib/libm.so.6 (0x00007fc4b5f29000)
libpthread.so.0 => /lib/libpthread.so.0 (0x00007fc4b5d0a000)
libdl.so.2 => /lib/libdl.so.2 (0x00007fc4b5b06000)
librt.so.1 => /lib/librt.so.1 (0x00007fc4b58fe000)
/lib/ld-linux-x86-64.so.2 => /lib64/ld-linux-x86-64.so.2 (0x00007fc4b6672000)
glibc does not support building statically, but musl does:
$ zig build-exe --c-source hello.c --library c -target x86_64-linux-musl
$ ./hello
Hello world
$ ldd hello
not a dynamic executable
In this example, Zig built musl libc from source and then linked against it. The build of musl libc for x86_64-linux remains available thanks to the caching system, so any time this libc is needed again it will be available instantly.
This means that this functionality is available on any platform. Windows and macOS users can build Zig and C code, and link against libc, for any of the targets listed above. Similarly code can be cross compiled for other architectures:
$ zig build-exe --c-source hello.c --library c -target aarch64v8-linux-gnu
$ file hello
hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 2.0.0, with debug_info, not stripped
In some ways, Zig is a better C compiler than C compilers!
This functionality is more than bundling a cross-compilation toolchain along with Zig. For example, the total size of libc headers that Zig ships is 22MiB uncompressed. Meanwhile, the headers for musl libc + linux headers on x86_64 alone are 8MiB, and for glibc are 3.1MiB (glibc is missing the linux headers), yet Zig currently ships with 40 libcs. With a naive bundling that would be 444MiB. However, thanks to this process_headers tool that I made, and some good old manual labor, Zig binary tarballs remain roughly 30MiB total, despite supporting libc for all these targets, as well as compiler-rt, libunwind, and libcxx, and despite being a clang-compatible C compiler. For comparison, the Windows binary build of clang 8.0.0 itself from llvm.org is 132MiB.
Note that only the Tier 1 Support targets have been thoroughly tested. It is planned to add more libcs (including for Windows), and to add test coverage for building against all the libcs. Support for building the FreeBSD libc startup files is the main feature needed to achieve Tier 1 Support for the target.
Thanks to Jay Weisskopf for helping with the glibc targets by removing an obsolete compat shim file.
Although Zig ships with libc for some targets, it still supports building against the system-native libc.
0.4.0 introduces a new command zig libc
which prints
the various paths of libc files. It outputs them to stdout
in a simple text file format that it is capable of parsing.
You can use zig libc libc.txt
to validate a file.
Here's what it looks like on my NixOS laptop:
$ zig libc
# The directory that contains `stdlib.h`.
# On POSIX-like systems, include directories be found with: `cc -E -Wp,-v -xc /dev/null`
include_dir=/nix/store/q2q1sg5sljia8sihhwcpbxir70yw33bw-glibc-2.27-dev/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=/nix/store/q2q1sg5sljia8sihhwcpbxir70yw33bw-glibc-2.27-dev/include
# The directory that contains `crt1.o`.
# On POSIX, can be found with `cc -print-file-name=crt1.o`.
# Not needed when targeting MacOS.
crt_dir=/nix/store/fivq0nbggp4y8mhy3ixprqd7qyn1hy2j-glibc-2.27/lib
# 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=
And here's what it looks like on my Windows laptop:
> zig.exe libc
# The directory that contains `stdlib.h`.
# On POSIX-like systems, include directories be found with: `cc -E -Wp,-v -xc /dev/null`
include_dir=C:\Program Files (x86)\Windows Kits\10\\Include\10.0.17134.0\ucrt
# 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=C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.15.26726\lib\x64\\..\..\include
# The directory that contains `crt1.o`.
# On POSIX, can be found with `cc -print-file-name=crt1.o`.
# Not needed when targeting MacOS.
crt_dir=C:\Program Files (x86)\Windows Kits\10\\Lib\10.0.17134.0\ucrt\x64\
# The directory that contains `vcruntime.lib`.
# Only needed when targeting MSVC on Windows.
msvc_lib_dir=C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.15.26726\lib\x64\
# The directory that contains `kernel32.lib`.
# Only needed when targeting MSVC on Windows.
kernel32_lib_dir=C:\Program Files (x86)\Windows Kits\10\\Lib\10.0.17134.0\um\x64\
These arguments are gone:
--libc-lib-dir [path] directory where libc crt1.o resides --libc-static-lib-dir [path] directory where libc crtbegin.o resides --msvc-lib-dir [path] (windows) directory where vcruntime.lib resides --kernel32-lib-dir [path] (windows) directory where kernel32.lib resides
Instead we have this argument:
--libc [file] Provide a file which specifies libc paths
This is used to pass a libc text file (which can be generated with
zig libc
). So it is easier to manage multiple cross compilation
environments.
When Zig must depend on the native libc installation, it first looks for
zig-cache/native_libc.txt
. If found, this file will be used. If this
file does not exist or has a parse error, Zig will perform native libc detection
and then overwrite this file. This can save time, because native libc detection can
be time consuming. For example it may need to invoke the system C compiler several
times to find out paths of things, or it may invoke some Windows COM API which is a bit
non-deterministic in terms of performance.
The --static
CLI option is removed and the -dynamic
option is added.
Instead of an explicit --static
flag, Zig makes things as static
as possible by default. Even zig build-lib
will now create a static
library by default, but -dynamic
can be used to choose a dynamic
library.
When targeting Linux, Zig now automatically links statically when not linking against
any shared libraries. It also disables Position Independent Code in this case, which
improves performance, especially with regards
to Thread-Local Variables. --enable-pic
and --disable-pic
can be used to override the default, but this
should be a rare use case.
Position Independent Code is also disabled by default for the freestanding OS target. When targeting Windows, Position Independent Code is always enabled.
It is proposed to rename these options to -fPIC
and -fno-PIC
to match C compilers.
For some use cases, it is known at compile-time that a given application or executable will never participate in multi-threading. For these use cases Zig has Single-Threaded Builds.
There is now a compile option --single-threaded
which has the following effects:
@import("builtin").single_threaded
becomes
true
and therefore various userland APIs which read this variable become more efficient:std.Mutex
becomes an empty data structure and all of its functions become no-ops.std.heap.ThreadSafeFixedBufferAllocator
alias std.heap.FixedBufferAllocator
when
--single-threaded
. #1910std.os.exit
use the
exit_group
syscall when not single-threaded.
The feature is exposed in the Zig Build System with
foo.single_threaded = true;
When one invokes the zig build
command, this executes a user-defined
build script. This is called the zig build system. It received
a few improvements during this release cycle.
Notably, Build Artifact Caching is enabled by default.
This means that by default build artifacts are now output into the
zig-cache
directory. The output path is not predictable and if the build
script desires to run an executable, rather than hard coding a path and using
builder.addCommand
, build scripts
must use executable.run
API, or addArtifactArg
.
An example of this can be found in the
tetris example.
builder.addSystemCommand
is available to run commands that are not
created by the build system itself. An example of this can be found in the
Raspberry Pi OS example.
Another option build scripts have is to specify the output directory of an artifact with
artifact.setOutputDir
. This disables caching and makes
artifact.getOutputPath
,
artifact.getOutputLibPath
, and
artifact.getOutputHPath
available.
Finally, the best way to make the output path of an artifact predictable is to install it with the build system's install step. There are some ergonomics and defaults to improve with this strategy, which will be tackled during the next release cycle.
Please note that Zig does no cache evicting yet. You may have to manually delete zig-cache directories periodically to keep disk usage down. It's planned for this to be a simple Least Recently Used eviction system eventually.
--output
, --output-lib
, and --output-h
are removed.
Instead, use --output-dir
which defaults to the current working directory.
Or take advantage of --cache on
, which will print the main output
path to stdout, and the other artifacts will be in the same directory
with predictable file names. --disable-gen-h
is available when
one wants to prevent .h file generation.
@cImport is always independently cached now.
It always writes the generated Zig code to disk which makes debug
info and compile errors better. No more "TODO: remember C source
location to display here" #2015.
The --verbose-cimport
command no longer dumps Zig AST to stderr; instead
it prints the file paths of the generated .zig files for inspection. In addition to
this improved behavior, the common case of @cImport
with all the
C header files unchanged is now fast due to being cached, yet still correct if any of
the C files change.
In addition, these improvements were made:
os_self_exe_path
to determine exe path not argv[0]
linkLibrary
will make the target depend on libc if the source does.builder.addFmt
API and use it to test stage1 zig fmt. #1968--static
. #2027zig build --help
more friendly. (#2194)LibExeObjStep.disable_gen_h
.
It is sometimes useful to skip generating of the header file (e.g.
#2173), and zig compiler
provides an option --disable-gen-h
to control that behaviour. However,
setting lib.disable_gen_h = true
in a typical build.zig
didn't append
the option to arguments. This commit fixes it and adds a convenient setDisableGenH
setter.
Zig comes with a robust caching system. When compiling Zig code, Zig knows all the files that
it depends on, and when compiling C code, Zig uses -MV -MD -MF
parameters to
Clang and then parses the generated .d file.
The other component of this is the "compiler id" concept:
$ zig id
Cxb7A-sAjt6VDq35pHZj3ACcwiSFqcCndt0NJkdQQlZVKolwm-QKnce1KEMUTqPr
This is the hash of the compiler binary itself, plus all the dynamic libraries it depends on,
directly or indirectly. That means if you fix a bug in memcpy
in the system libc,
Zig will detect this change and the value of zig id
will be different.
However, the value is quickly computed because it also participates in the caching system.
Here is a "manifest file" in the caching system for zig id
:
4850313 1554740311 416647968 cOOP0xpsfiYfuMJCS3V_fLKdNmxFxLih_UACAUqwynGp1Y616NJ2lHL0hOpWOp_o /home/andy/dev/zig/build/zig
3077799 1 0 bBgfkt9ZSVN_HgBbzwHhzKmQxpssdDYq_HcbEkdu7KDcUhanATUER2_5l_U__9DE /nix/store/5dphwv1xs46n0qbhynny2lbhmx4xh1fc-zlib-1.2.11/lib/libz.so.1
1165671 1 0 jSVrSQyNkPFoz0Z6GesezdVP1ZBRpHL2UbbX7HbNEprcU4aBuIyG9z6bAOvvPZr0 /nix/store/fivq0nbggp4y8mhy3ixprqd7qyn1hy2j-glibc-2.27/lib/librt.so.1
1165626 1 0 scJm8rYLhDLp_SDXmbvdlcj9fEWQadEm_kHUBJn2oGnht3pSh2MNzsUJZjR1wwa1 /nix/store/fivq0nbggp4y8mhy3ixprqd7qyn1hy2j-glibc-2.27/lib/libdl.so.2
1165664 1 0 pGDNQn5x_j1NAWzjh4AOUP8OKyJj1KSPd4SJYzRVbTTp7TRb4IQpDs7ZAU3muqfz /nix/store/fivq0nbggp4y8mhy3ixprqd7qyn1hy2j-glibc-2.27/lib/libpthread.so.0
1165631 1 0 4tmxoKlbVwB42gYxT7B3CKIBXpbGdb_vuqfYiXlOZdyngmFJdQ7DBZxWqZKSO0Ss /nix/store/fivq0nbggp4y8mhy3ixprqd7qyn1hy2j-glibc-2.27/lib/libm.so.6
3077645 1 0 dxe6qw_yMCi8Bp7ocrVcl2XMdRlKfa0S3ATqbxsEMISRQdQ1dyxTEE1uDbv0R54z /nix/store/sf0wnp30savqz9ljn6fsrn8f63w5v0za-gcc-7.4.0-lib/lib/libstdc++.so.6
1165630 1 0 5boikua2jQubswAtj8MICJzjmGNUxTZrNreQm_DmtspgRsXULyCkyUUMYcNFamII /nix/store/fivq0nbggp4y8mhy3ixprqd7qyn1hy2j-glibc-2.27/lib/libgcc_s.so.1
1165616 1 0 qiXB510da3ryfL7LcPUvL2EQgY2tIwszLRvgQdDrQtnWVl4zA1Nfpq4SDpIz3IYW /nix/store/fivq0nbggp4y8mhy3ixprqd7qyn1hy2j-glibc-2.27/lib/libc.so.6
1165607 1 0 MpHwajxu3h0TtsHViKMAmHHeGMbi9IuDLlybX6PpqT66Xfa1h2GAT3XFIX19dEz- /nix/store/fivq0nbggp4y8mhy3ixprqd7qyn1hy2j-glibc-2.27/lib/ld-linux-x86-64.so.2
The fields are, respectively:
This same system is used for build artifact caching. For example, here is the manifest file
for hello world in Zig (when built with --cache on
):
5505089 1548876783 204467480 rcnRrxBgZSiWki_XN9XKlQ2yfWkM6KLYhUWprzniEBtjgmeUSmtlv5mAguA4l2Q1 /home/andy/dev/zig/example/hello_world/hello.zig
4850617 1554305190 0 GXP-IfocPj934vVJYrccBKIVoOcWULaUtkBoR-iHnEKEIoXtdchyuQi8tk8FTncS /home/andy/dev/zig/build/lib/zig/std/std.zig
5114024 1551557778 0 6u-vFMcHH_FcfkAyqCcoYXAfF2TLMCkoP86Q3Ykecmage7_E4ObRsfP52-YXDHH6 /home/andy/dev/zig/build/lib/zig/std/special/panic.zig
4850617 1554305190 0 GXP-IfocPj934vVJYrccBKIVoOcWULaUtkBoR-iHnEKEIoXtdchyuQi8tk8FTncS /home/andy/dev/zig/build/lib/zig/std/std.zig
4850277 1553179780 0 9S5aQg0ORbkQnvDCVeW2aNPSeCu993I4iO3JoGrif4py7zBS4si4mCaeHHuo9TEn /home/andy/dev/zig/build/lib/zig/std/debug.zig
4849956 1554500038 0 8c76uDlKxK5jZ1L0Uup4Lu0y1NlU5-KrUCKFyZnzoi7Hv3dDByN8pvujTjxjaf0m /home/andy/dev/zig/build/lib/zig/std/os.zig
4850678 1554330534 0 wFvlet_2y9YT6NlFrsqybubwMafHFCIZqq4AxLlcUGuTSZ7VjI8kGmM-3VKPrhkt /home/andy/dev/zig/build/lib/zig/std/io.zig
5112957 1551560716 0 f29y95JhSfGVwEFrutPnquGPPuvvtOLu6_vUUgGbaqn-9uulgFvBnyPU8Zp_sH7r /home/andy/dev/zig/build/lib/zig/std/os/file.zig
5113386 1552707664 0 Mb9m-jj_wRe6yt-TFko9wUGNToAA-IAbVfmR2TYZyPjKP3D87df6BGALjai5uKr2 /home/andy/dev/zig/build/lib/zig/std/os/windows/util.zig
5113248 1551562209 0 F1infSSAdOgbOWl3y2FdUiB-6xrpTaeqUJ5xt_F9IZOeBvvTSLNpkGEg8mrYMcli /home/andy/dev/zig/build/lib/zig/std/os/linux.zig
5113251 1536869149 0 LAlDVvl1TX-3-SB3MOBBjxuEk2Ms8SpkcYBLXnJONgZuo7aV9OYfXLfih9bfGyBS /home/andy/dev/zig/build/lib/zig/std/os/linux/errno.zig
5113278 1551560715 0 TpIIt3DNczfgMKjhDA5mx-4JgoGd7SIV9lXK8GQs9Bix4EmPiL7VlV1oQVwv4gkO /home/andy/dev/zig/build/lib/zig/std/os/linux/x86_64.zig
4849832 1554482295 0 AszHJ9beyMCotgQx4vwYWMBUcRd1dBspsou64fATNYXsSqwB5zAiqVEokfpV5xkF /home/andy/dev/zig/build/lib/zig/std/fmt.zig
4850680 1551559869 0 Polq06O1t-jJ0SobBVqLaQrJVXaKzH4lQj6Na8NRN3Y5N0pZ2I4W2rpwL816MSz- /home/andy/dev/zig/build/lib/zig/std/io/seekable_stream.zig
4850605 1552687041 0 Al_VWoytLHeozYOvM69cCR2Dw1HGYdJc2lDGQDXYoiJy1VuD6GWLIQhxSJuf3Oza /home/andy/dev/zig/build/lib/zig/std/array_list.zig
4877695 1552687041 0 ouDIQ9yX5LXanXk6mc2ypFP-oy2zEt7uLLlPDVUFEfx4AKmeFP1cJWI7YSjYAVt9 /home/andy/dev/zig/build/lib/zig/std/mem.zig
4850649 1551560732 0 rVa_7s1plylcGIUAHEt3eGG8XwSJAEOQzuIaYcnq0KHnb1IoGWUEXUGIW7QHH7fi /home/andy/dev/zig/build/lib/zig/std/elf.zig
4849844 1554305190 0 58Tg88pYb1sgK8SI5_65Gy7lgCqaJmsW2NV-Hi4FeXOK8e2f2T1PofNfdUDgbYit /home/andy/dev/zig/build/lib/zig/std/math.zig
4850647 1536869149 0 9-R2p6FQ_7UIjQMt3N-chgeh-_e_o29Gr2Pybfh2PurmRYT9mIIwsdW9rs6kEJR8 /home/andy/dev/zig/build/lib/zig/std/dwarf.zig
5113295 1552687041 0 6oc1oIiWQJ1OIZGh5KFPtul93GCeAaLFx2VTICI3JMt-Ubnj-vjXvBBclnoo5UQ6 /home/andy/dev/zig/build/lib/zig/std/os/path.zig
4850677 1552707664 0 qom60op_QDptmhKO6SIlpm48SlVybcFOT0gHCjncJvtZhM6uUNqZZ2Q0WV5AsmRP /home/andy/dev/zig/build/lib/zig/std/heap.zig
4850683 1551560704 0 6t3PH8KaWwsz2MElBWZezzyZyeRS-Ttq6k2pJWwRK__NvVQ2hZSu1gk4jA7oDT_Y /home/andy/dev/zig/build/lib/zig/std/linked_list.zig
4850685 1548876783 0 EsROc7EV9MKNd74wRR9tYFhMGs4CL4eqYSPvJVzKTzSoXABBfxVoQwO3N_VivhNG /home/andy/dev/zig/build/lib/zig/std/macho.zig
4850663 1554667129 0 oWDxArJXQLKaeAqd-Uf52hiAjktfqEhJA4XIdlBKCeKOxbYZosChXsfReGM0azCQ /home/andy/dev/zig/build/lib/zig/std/hash_map.zig
4877700 1551559918 0 0SY3kFjpRYGQ1eVmh4UHluGBDFA9LcwANsSlEZLKfBO6hWxoZWcowtXk1SJvSALu /home/andy/dev/zig/build/lib/zig/std/pdb.zig
4850628 1551559906 0 QEww1cNolzNrXqhOMV8uMTXRXQmbekZ7nyUaEqj35x6ZjO8BRVzqvYxQeSmW8c_5 /home/andy/dev/zig/build/lib/zig/std/coff.zig
4877702 1551562259 0 9cFnTGm01DAKqMZim8dzEXoPyN8w3R8UAer53ycBv2PFgg-pM6AsNd6hDf0jlseT /home/andy/dev/zig/build/lib/zig/std/rand.zig
5113402 1554353707 0 m2d1Awh2J4qJuWpBI5sEZJyszBZB2VIZlHvVpXZlt9Vz9JdjXvMN2Cig4ohevME_ /home/andy/dev/zig/build/lib/zig/std/special/bootstrap.zig
4877697 1551560726 0 y2XL97U7jZ9PY4d5TVVZmBzYQc5wrCE_j_utDeU8zfLp5W6iLdFEf_HLbG4-W9Mf /home/andy/dev/zig/build/lib/zig/std/mutex.zig
4877707 1551560708 0 jd03IjTqKqI-sTTbTTL63EjXX2xp9vPInAn4fA0rRis3TeXGgWnP-44_8N0OTRPP /home/andy/dev/zig/build/lib/zig/std/spinlock.zig
The way the cache checking works, is that it calls fstat
on all the files in the
manifest. For each file, if the inodes and mtimes from the manifest match the results from
fstat
, then the hash is trusted to be up-to-date for that particular file.
Any files with outdated hashes, have their contents hashed, and the manifest file updated
with the updated hashes. Finally, all the hashes are hashed together, and that final hash
is used as the directory name within zig-cache
that Zig looks for previously
created build artifacts.
Because Zig uses -MD -MF
args to clang when doing
@cImport
, Zig has a complete list of files that the C code read from,
which Zig adds to the cache. So even when using @cImport
,
Zig's caching system remains perfect. This is a proof of concept
for the mechanism that the self-hosted compiler
will use to watch and rebuild files.
There is one really fascinating problem to solve when relying on file modification times to determine whether a file's contents have changed. Some file systems, such as macOS's HFS+, do not support nanosecond precision; only seconds. However even nanosecond precision exhibits the problem for very fast systems.
Imagine that a file was modified, and then immediately after, added to the cache. When added to the manifest, the current time and the file's mtime will be the same. If the file is modified again quickly, and then the cache checked, it will still be the same instant in time; the modification to the file did not change the mtime.
There is a beautiful solution to this problem, which is to disqualify a file from being cached if the current time, truncated to the file system time granularity, matches the mtime:
// If the wall clock time, rounded to the same precision as the
// mtime, is equal to the mtime, then we cannot rely on this mtime
// yet. We will instead save an mtime value that indicates the hash
// must be unconditionally computed.
static bool is_problematic_timestamp(const OsTimeStamp *fs_clock) {
OsTimeStamp wall_clock = os_timestamp_calendar();
// First make all the least significant zero bits in the fs_clock, also zero bits in the wall clock.
if (fs_clock->nsec == 0) {
wall_clock.nsec = 0;
if (fs_clock->sec == 0) {
wall_clock.sec = 0;
} else {
wall_clock.sec &= (-1ull) << ctzll(fs_clock->sec);
}
} else {
wall_clock.nsec &= (-1ull) << ctzll(fs_clock->nsec);
}
return wall_clock.nsec == fs_clock->nsec && wall_clock.sec == fs_clock->sec;
}
What's beautiful about this is that it generalizes to any granularity. If a file system had a 1 day mtime granularity, then this would mean any files modified within 1 day would have to have their contents hashed, but files older than that could have their hashes trusted. If a file system always wrote 0 for the mtime, Zig would never trust it.
Thanks to apenwarr's insightful blog post about build system caching for inspiration.
The caching system is disabled by default for the command line interface, except for zig test
. zig test
does respect --output-dir
however, which disables caching.
The default caching behavior can be overridden with --cache [on|off]
.
The Zig Build System enables caching by default.
Caching is always enabled, using the global cache directory, when building libc and compiler-rt. This saves a considerable amount of accumulated time, since they must be available for every compilation.
Zig's compatibility with Valgrind has improved. Notably,
Valgrind 3.14 fixes
a bug where Zig programs' debug symbols
would not be detected. Because of this, the --no-rosegment
command line option is removed from Zig. It was only meant to be a temporary workaround.
Thanks to daurnimator for adding a valgrind module to the standard library. This allows Zig code to easily make Valgrind client requests.
In addition to userland API, Zig integrates more tightly with Valgrind. For example,
one thing that Zig does in Debug builds is
write 0xaa
bytes to undefined
variables:
undef.zig
const std = @import("std");
pub fn main() void {
var array: [10]u8 = undefined;
std.debug.warn("array: {x}\n", array);
}
$ zig build-exe undef.zig
$ ./undef
array: aaaaaaaaaaaaaaaaaaaa
Valgrind has detection for branching on undefined values, but if Zig was writing
0xaa
values to uninitialized variables, Valgrind would not know
that this is supposed to be an undefined canary. It would see the values as defined.
However thanks to integrated Valgrind client requests with the Zig language, Valgrind
knows what's going on:
undef.zig
const std = @import("std");
pub fn main() void {
var x: i32 = 1234;
std.debug.warn("x = {}\n", x);
x = undefined;
if (x > 10) {
std.debug.warn("greater than 10\n");
}
}
$ zig build-exe undef.zig
$ ./undef
x = 1234
When run in valgrind:
==16484== Conditional jump or move depends on uninitialised value(s)
==16484== at 0x224B6C: main (undef.zig:7)
==16484== by 0x22455A: posixCallMainAndExit (bootstrap.zig:86)
==16484== by 0x224350: _start (bootstrap.zig:43)
This is a debugging tool available in Zig that is not available in C -
setting things to undefined
so that Valgrind can
catch it if you accidentally use it.
Language integration with Valgrind is enabled by default in
Debug mode, and disabled by default in other
modes. The feature can be force enabled or force disabled with
--enable-valgrind
and --disable-valgrind
, respectively.
Also available is @import("builtin").valgrind_support
which is a
comptime
bool
available for code to
find out whether the programmer wants the executable to have Valgrind integration.
It's always disabled for compiler_rt and libc/builtin.
The cost of this feature is a few assembly instructions with each assignment to
undefined
. Only support for Linux, macOS, Solaris, and
MinGW on x86_64 is currently implemented.
zig fmt
is a tool to format your Zig code to a canonical style.
It's implemented in Zig and it's been available as a
self-hosted compiler subcommand for a while,
but the self-hosted compiler is not done yet and it's not what's available on
the download page.
Zig 0.4.0 ships with zig fmt
capabilities. Under the hood, it uses
zig run
and the Build Artifact Caching system, to lazily build
zig fmt
from source, and then run it.
That means even the stage1 C++ Zig compiler is a sort of hybrid - part C++, and part Zig.
Several editors have zig fmt
integration available, such as the
vim plugin that I use.
I believe the VSCode plugin does as well.
There is now has a --check
flag, which does everything
except modify file contents. That is, it lists non-conforming files on stdout,
and then exits with an error if the list is non-empty. #1558 #1555
Other improvements during this release cycle:
this
as a keyword since it was
removed in 0.3.0.There is also a discussion about whether or not to align struct fields, which has some interesting arguments on either side.
I have come up with a plan to migrate translate-c to userland:
This will allow us to move thousands of lines of C++ to Zig, and to only have to maintain one implementation of C translation rather than two. This same strategy could be used for other pieces of the Zig project, such as the package manager.
I started this effort with a proof of concept, but there is still a long way to go before this is done.
In addition the following improvements were made:
--verbose-cimport
prints clang commandassert
macro from glibc.Jimmi lead this effort. Starting with solving the return type ambiguity (#1628), he then moved on to create zig-spec, update the grammar section of the language reference, and update the stage1 parser to conform.
Thanks to his efforts, Zig syntax now has a formal grammar specification and three independent implementations, despite the fact that the syntax is not fully stablized.
The language reference manual. now has a light theme by default, but respects the user's light/dark preference via the prefers-color-scheme media query. Most browsers don't support this yet, so we just have to wait patiently for the future to arrive.
Instead of a side bar index, the index is inline with the rest of the content. This is simpler and more friendly to all user agents, and means we don't need the media query for mobile devices. It also makes back-references work, so now headers link to the table of contents and the table of contents links to headers.
Both of these things apply to this release notes document as well as the language reference manual.
There is still no HTML documentation available for the Zig Standard Library. See the Roadmap for more details.
The master branch language reference manual is now automatically updated on master branch commits that pass all tests on all platforms.
In addition to the above, the following changes were made to the documentation:
section
keyword to linksection
. #1152T
to *const T
. #1465
*T
to ?*const T
T
to ?*const T
when T
is a struct/union@minValue
,@maxValue
; add std.math.minInt
and std.math.maxInt
. #1466 #1476comptime
,
so function calls, for example, will be evaluated without the need for an explicit
comptime
block. Similarly:
**
and ++
operators force comptime
on operands. #1707@sizeOf(T)
or
@typeInfo(T).Int.bits
.
@sizeOf(T) >= @alignOf(T)
.
It also fixes std.mem.secureZero
for integers where this guarantee
previously was breached, and it fixes std.mem.Allocator
for integers
where this guarantee previously was breached. #1851 #1864union(enum)
. #1711usize
rather than a pointer.builtin.TypeInfo.ErrorSet
is now ?[]Error
instead of struct{errors:[]Error}
. #1936*const T
and ?*T
. #1298allowzero
pointer attribute. Only useful for
freestanding targets. Also adds safety for
@intToPtr when the address is zero. #1953All numbers with comptime known values now implicitly cast to all number types. If the value does not fit, a compile error is emitted.
comptime_ints.zig
const std = @import("std");
const assert = std.debug.assert;
test "implicit cast large integer to smaller with comptime value" {
const x: u64 = 255;
const y: u8 = x;
assert(y == 255);
}
$ zig test comptime_ints.zig
Test 1/1 implicit cast large integer to smaller with comptime value...OK
All tests passed.
Zig 0.4.0 ships with a new Vector type. This feature is brand new and not well documented yet. Here are some example tests:
vectors.zig
const std = @import("std");
const mem = std.mem;
const expect = std.testing.expect;
test "vector wrap operators" {
const S = struct {
fn doTheTest() void {
var v: @Vector(4, i32) = [4]i32{ 2147483647, -2, 30, 40 };
var x: @Vector(4, i32) = [4]i32{ 1, 2147483647, 3, 4 };
expect(mem.eql(i32, ([4]i32)(v +% x), [4]i32{ -2147483648, 2147483645, 33, 44 }));
expect(mem.eql(i32, ([4]i32)(v -% x), [4]i32{ 2147483646, 2147483647, 27, 36 }));
expect(mem.eql(i32, ([4]i32)(v *% x), [4]i32{ 2147483647, 2, 90, 160 }));
var z: @Vector(4, i32) = [4]i32{ 1, 2, 3, -2147483648 };
expect(mem.eql(i32, ([4]i32)(-%z), [4]i32{ -1, -2, -3, -2147483648 }));
}
};
S.doTheTest();
comptime S.doTheTest();
}
test "vector int operators" {
const S = struct {
fn doTheTest() void {
var v: @Vector(4, i32) = [4]i32{ 10, 20, 30, 40 };
var x: @Vector(4, i32) = [4]i32{ 1, 2, 3, 4 };
expect(mem.eql(i32, ([4]i32)(v + x), [4]i32{ 11, 22, 33, 44 }));
expect(mem.eql(i32, ([4]i32)(v - x), [4]i32{ 9, 18, 27, 36 }));
expect(mem.eql(i32, ([4]i32)(v * x), [4]i32{ 10, 40, 90, 160 }));
expect(mem.eql(i32, ([4]i32)(-v), [4]i32{ -10, -20, -30, -40 }));
}
};
S.doTheTest();
comptime S.doTheTest();
}
test "vector float operators" {
const S = struct {
fn doTheTest() void {
var v: @Vector(4, f32) = [4]f32{ 10, 20, 30, 40 };
var x: @Vector(4, f32) = [4]f32{ 1, 2, 3, 4 };
expect(mem.eql(f32, ([4]f32)(v + x), [4]f32{ 11, 22, 33, 44 }));
expect(mem.eql(f32, ([4]f32)(v - x), [4]f32{ 9, 18, 27, 36 }));
expect(mem.eql(f32, ([4]f32)(v * x), [4]f32{ 10, 40, 90, 160 }));
expect(mem.eql(f32, ([4]f32)(-x), [4]f32{ -1, -2, -3, -4 }));
}
};
S.doTheTest();
comptime S.doTheTest();
}
test "vector bit operators" {
const S = struct {
fn doTheTest() void {
var v: @Vector(4, u8) = [4]u8{ 0b10101010, 0b10101010, 0b10101010, 0b10101010 };
var x: @Vector(4, u8) = [4]u8{ 0b11110000, 0b00001111, 0b10101010, 0b01010101 };
expect(mem.eql(u8, ([4]u8)(v ^ x), [4]u8{ 0b01011010, 0b10100101, 0b00000000, 0b11111111 }));
expect(mem.eql(u8, ([4]u8)(v | x), [4]u8{ 0b11111010, 0b10101111, 0b10101010, 0b11111111 }));
expect(mem.eql(u8, ([4]u8)(v & x), [4]u8{ 0b10100000, 0b00001010, 0b10101010, 0b00000000 }));
}
};
S.doTheTest();
comptime S.doTheTest();
}
$ zig test vectors.zig
Test 1/4 vector wrap operators...OK
Test 2/4 vector int operators...OK
Test 3/4 vector float operators...OK
Test 4/4 vector bit operators...OK
All tests passed.
Note that Zig of course supports vectors at comptime
and likewise
all the safety features of arithmetic operators are available for vectors as well.
Vectors provide a way to do Single Instruction Multiple Data in a portable and efficient way. It's much easier for the optimizer to break vectors into scalars than it is to turn scalars into vectors. Even in debug builds which have safety checks, vector operations provide more data throughput than scalar operations.
Thanks to Jimmi Holst Christensen for many contributions to vector support.
To follow progress on vectors and SIMD in Zig, watch issue #903.
Zig's comptime
facilities are improved to support
reinterpreting memory, as long as the types in question have guaranteed
in-memory representation.
bitcast.zig
const builtin = @import("builtin");
const std = @import("std");
const expect = std.testing.expect;
test "@bitCast extern structs at runtime and comptime" {
const Full = extern struct {
number: u16,
};
const TwoHalves = extern struct {
half1: u8,
half2: u8,
};
const S = struct {
fn doTheTest() void {
var full = Full{ .number = 0x1234 };
var two_halves = @bitCast(TwoHalves, full);
switch (builtin.endian) {
builtin.Endian.Big => {
expect(two_halves.half1 == 0x12);
expect(two_halves.half2 == 0x34);
},
builtin.Endian.Little => {
expect(two_halves.half1 == 0x34);
expect(two_halves.half2 == 0x12);
},
}
}
};
S.doTheTest();
comptime S.doTheTest();
}
test "reinterpret bytes of an array into an extern struct" {
testReinterpretBytesAsExternStruct();
comptime testReinterpretBytesAsExternStruct();
}
fn testReinterpretBytesAsExternStruct() void {
var bytes align(2) = []u8{ 1, 2, 3, 4, 5, 6 };
const S = extern struct {
a: u8,
b: u16,
c: u8,
};
var ptr = @ptrCast(*const S, &bytes);
var val = ptr.c;
expect(val == 5);
}
$ zig test bitcast.zig
Test 1/2 @bitCast extern structs at runtime and comptime...OK
Test 2/2 reinterpret bytes of an array into an extern struct...OK
All tests passed.
These examples work because extern struct
and packed struct
have guaranteed in-memory layout.
In addition, as long as the memory is never accessed, pointers with hard coded addresses
and pointer arithmetic are supported at comptime
:
comptime_ptrs.zig
const std = @import("std");
const expect = std.testing.expect;
test "C pointer comparison and arithmetic" {
const S = struct {
fn doTheTest() void {
var one: usize = 1;
var ptr1: [*c]u32 = 0;
var ptr2 = ptr1 + 10;
expect(ptr1 == 0);
expect(ptr1 >= 0);
expect(ptr1 <= 0);
expect(ptr1 < 1);
expect(ptr1 < one);
expect(1 > ptr1);
expect(one > ptr1);
expect(ptr1 < ptr2);
expect(ptr2 > ptr1);
expect(ptr2 >= 40);
expect(ptr2 == 40);
expect(ptr2 <= 40);
ptr2 -= 10;
expect(ptr1 == ptr2);
}
};
S.doTheTest();
comptime S.doTheTest();
}
$ zig test comptime_ptrs.zig
Test 1/1 C pointer comparison and arithmetic...OK
All tests passed.
A variable may be specified to be a thread-local variable using the
threadlocal
keyword:
tls.zig
const std = @import("std");
const assert = std.debug.assert;
threadlocal var x: i32 = 1234;
test "thread local storage" {
const thread1 = try std.os.spawnThread({}, testTls);
const thread2 = try std.os.spawnThread({}, testTls);
testTls({});
thread1.wait();
thread2.wait();
}
fn testTls(context: void) void {
assert(x == 1234);
x += 1;
assert(x == 1235);
}
$ zig test tls.zig
Test 1/1 thread local storage...OK
All tests passed.
For Single-Threaded Builds, all thread local variables are treated as Global Variables.
Thread local variables may not be const
.
Note that thread local variables have better performance for non- position independent code, which is one reason for Static by Default.
Thanks to Rich Felker from the musl libc project, who gave me this crucial information, when I was implementing thread local storage for static builds:
to satisfy the abi, your init code has to write the same value to that memory location as the value passed to the [arch_prctl] syscall
This deletes some legacy cruft, and produces leaner object files. Example:
var x: i32 = 1234;
export fn entry() i32 {
return x;
}
This produced:
@x = internal unnamed_addr global i32 1234, align 4
@0 = internal unnamed_addr constant i32* @x, align 8
and @0 was never even used. After Zig 0.4.0, @0 is not produced.
This fixes a bug: Zig was creating invalid LLVM IR when one of these globals that shouldn't exist takes the address of a thread local variable. In LLVM 8, it would produce a linker error. But probably after my bug report is solved it will be caught by the IR verifier.
Thanks to Rui Ueyama and George Rimar who helped me troubleshoot this problem when I filed an LLVM bug report.
One of the use cases of Zig is to beat C at its own game, and this means being a better
language for using C libraries than C is.
It's a tall order to fulfill, and it was compromised by Zig's
pointer reform. An unfortunate
side effect of having more type safety in pointer types was that C's very unsafe pointer types
became awkward to use. It is ambiguous whether pointers should be translated as
single-item pointers (*T
) or unknown-length pointers ([*]T
).
C Pointers are a compromise so that Zig code can utilize translated header files directly.
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.
[*c]T
- C pointer.
?usize
. Note that creating an optional C pointer
is unnecessary as one can use normal Optional Pointers.
It's planned to support `if`, `orelse`, `null`, and `.?` for C pointers.
While C pointers themselves have all the footguns associated with, well, C pointers, there is safety when converting to Zig pointers:
test.zig
test "cast null C pointer to Zig pointer" {
var c_ptr: [*c]i32 = 0;
var zig_ptr: *i32 = c_ptr;
}
$ zig test test.zig
Test 1/1 cast null C pointer to Zig pointer...cast causes pointer to be null
/home/andy/dev/www.ziglang.org/docgen_tmp/test.zig:3:25: 0x2040f1 in ??? (test)
var zig_ptr: *i32 = c_ptr;
^
/home/andy/dev/zig/build/lib/zig/std/special/test_runner.zig:13:25: 0x225bdb in ??? (test)
if (test_fn.func()) |_| {
^
/home/andy/dev/zig/build/lib/zig/std/special/bootstrap.zig:122:22: 0x225366 in ??? (test)
root.main() catch |err| {
^
/home/andy/dev/zig/build/lib/zig/std/special/bootstrap.zig:43:5: 0x2250d1 in ??? (test)
@noInlineCall(posixCallMainAndExit);
^
Tests failed. Use the following command to reproduce the failure:
/home/andy/dev/www.ziglang.org/docgen_tmp/test
In Zig 0.3.0, @typeOf(@import("builtin"))
is (import)
,
which was a special "namespace" type. In Zig 0.4.0,
@typeOf(@import("builtin"))
is type
.
The special "namespace" type is removed; instead all files are structs with no fields.
This makes the language smaller and simpler, and makes imported files easier to inspect with reflection, since they are structs.
With this change, struct types get fully qualified names and function symbol names
become fully qualified. This means that when using zig test
it's no longer
necessary to manually namespace test names as they will be automatically namespaced.
There is a new compile error for importing a file outside the package path.
The new CLI option --main-pkg-path
can be used to choose
a different root package directory besides the one inferred from the
root source file. The corresponding Zig Build System API is
artifact.setMainPkgPath(path)
.
Zig has a new type to make dealing with enums more attractive. It can be thought of as the
enum
equivalent of comptime_int
. The enum
equivalent of a number literal.
enum-lit.zig
const std = @import("std");
pub fn main() void {
print(1234, .hex);
print(1234, .normal);
}
fn print(x: i32, mode: enum { normal, hex }) void {
switch (mode) {
.normal => std.debug.warn("{}\n", x),
.hex => std.debug.warn("0x{x}\n", x),
}
}
$ zig build-exe enum-lit.zig
$ ./enum-lit
0x4d2
1234
Here you can see an anonymous
enum
was used rather than an opaque bool
parameter.
The idea here is to make it so easy to have meaningful values, that programmers will want
to use them, even if they're feeling lazy.
The ergonomics of this feature feel great. Here's tgschultz updating some of the standard library API to use anonymous enum literals.
Note that this feature is checked by the compiler. It's completely safe:
test.zig
const std = @import("std");
test "invalid enum literal" {
print(1234, .hex);
print(1234, .invalid);
}
fn print(x: i32, mode: enum { normal, hex }) void {
switch (mode) {
.normal => std.debug.warn("{}\n", x),
.hex => std.debug.warn("0x{x}\n", x),
}
}
$ zig test test.zig
/home/andy/dev/www.ziglang.org/docgen_tmp/test.zig:5:17: error: enum 'enum:8:24' has no field named 'invalid'
print(1234, .invalid);
^
/home/andy/dev/www.ziglang.org/docgen_tmp/test.zig:8:24: note: 'enum:8:24' declared here
fn print(x: i32, mode: enum { normal, hex }) void {
^
std.debug.failing_allocator
is now a *Allocator
rather than the FailingAllocator
state. -Jimmi Holst Christensenstd.event.tcp
to std.event.net
.std.io.FileInStream
to std.os.File.InStream
std.io.FileInStream.init(file)
to file.inStream()
std.io.FileOutStream
to std.os.File.OutStream
std.io.FileOutStream.init(file)
to file.outStream()
std.event.net.socketRead
to std.event.net.read
std.event.net.socketWrite
to std.event.net.write
std.event.net.readv
std.event.net.writev
std.event.net.readvPosix
std.event.net.writevPosix
std.event.net.OutStream
std.event.net.InStream
std.event.io.InStream
std.event.io.OutStream
std.io.InStream.read
now can return less than buffer sizestd.io.InStream.readFull
for previous behaviorstd.os.File.openWriteNoClobberC
std.os.deleteFileWindows
to std.os.deleteFileW
std.os.deleteFilePosix
std.os.deleteFileC
std.os.copyFile
no longer takes an allocatorstd.os.copyFileMode
no longer takes an allocatorstd.os.AtomicFile
no longer takes an allocatorstd.os.renameW
std.os.renameC
std.os.linux.vfork
and std.os.linux.exit_group
std.os.time.sleep(seconds: usize, nanoseconds: usize)
to (nanoseconds: u64)
. -Marc Tiehuisstd.math.maxInt
and std.math.minInt
std.crypto.HmacBlake2s256
std.net.Address.port
-Josh Wolfestd.io.NullOutStream
and std.io.CountingOutStream
-Jimmi Holst Christensenstd.io.InStream(E).readStruct
returns a value instead of taking a pointer. -Jimmi Holst Christensenstd.atomic.Int.set
. -Josh Wolfestd.meta.intToEnum
.std.rand.Rand
API to better handle the
concept of bias. -Josh Wolfestd.rand.Rand.uintLessThanBiased
std.rand.Rand.uintAtMostBiased
std.rand.Rand.intRangeLessThanBiased
std.rand.Rand.intRangeAtMostBiased
std.HashMap.getOrPutValue
. -Jimmi Holst Christensenstd.io.readLine
std.io.readLine
has a new prototypestd.io.readLineFrom
std.io.readLineSlice
std.io.readLineSliceFrom
std.io.SeekableStream
. Dwarf debug info
is modified to use this instead of std.os.File
directly to make it easier for
bare metal projects to take advantage of debug info parsing.std.debug.StackIterator
.error.DeviceBusy
as a possible result of std.os.posixOpen
. I observed EBUSY
when trying to open for writing a tty fd that is already opened with screen.std.debug.assert
no longer has special behavior for test builds.
Use std.testing.expect
to detect test failures in a way that will
work in release builds. See assert vs expect.
std.mem.Allocator.create
is removed.
std.mem.Allocator.createOne
is renamed to
std.mem.Allocator.create
.
The problem with the previous API is that even after copy elision,
the initalization value passed as a parameter would always be a copy.
With the new API, once copy elision is done, initialization
functions can directly initialize allocated memory in place. #1872 #1873
std.mem.split
is removed.
std.mem.separate
and std.mem.tokenize
are added.
std.fmt.parseFloat
.
This is not intended to be the long-term implementation as it doesn't
provide various properties that we eventually will want (e.g.
round-tripping, denormal support). It also uses f64
internally so the
wider f128
will be inaccurate. See #2207 for robust float parsing
in the Zig Standard Library.std.PriorityQueue
.std.os.posixMProtect
.std.os.changeCurDir
no longer has an allocator parameter.std.ascii
. Rohlem
added test coverage and fixed the logic.std.math.mulWide
.std.crypto.HmacBlake2s256
.SegmentedList.shrink
.std.io.Serializer
.std.io.Deserializer
.std.io.BitInStream
.std.io.BitOutStream
.std.meta.TagPayloadType
.std.meta.trait.isUnsignedInt
.std.meta.trait.isSignedInt
.std.meta.stringToEnum
std.mem.separate
vs std.mem.tokenize
.std.math.IntFittingRange
.std.LinkedList.concat
.std.meta
- helper functions for doing reflection. #1662Previously, std.debug.assert
would
@panic in test builds,
if the assertion failed. Now, it's always unreachable
.
This makes release mode test builds more accurately test the actual code that will be run.
However this requires tests to call std.testing.expect
rather than std.debug.assert
to make sure output is correct.
Here is the explanation of when to use either one, copied from the assert doc comments:
Inside a test block, it is best to use the std.testing
module
rather than assert, because assert may not detect a test failure
in ReleaseFast and
ReleaseSmall modes.
Outside of a test block, assert is the correct function to use.
See #1304.
Zig code can use std.debug.captureStackTrace
at any time
for debugging purposes:
stack-traces.zig
const std = @import("std");
const builtin = @import("builtin");
var address_buffer: [8]usize = undefined;
var trace1 = builtin.StackTrace{
.instruction_addresses = address_buffer[0..4],
.index = 0,
};
var trace2 = builtin.StackTrace{
.instruction_addresses = address_buffer[4..],
.index = 0,
};
pub fn main() void {
foo();
bar();
std.debug.warn("first one:\n");
std.debug.dumpStackTrace(trace1);
std.debug.warn("\n\nsecond one:\n");
std.debug.dumpStackTrace(trace2);
}
fn foo() void {
std.debug.captureStackTrace(null, &trace1);
}
fn bar() void {
std.debug.captureStackTrace(null, &trace2);
}
$ zig build-exe stack-traces.zig
$ ./stack-traces
first one:
/home/andy/dev/www.ziglang.org/docgen_tmp/stack-traces.zig:27:32: 0x22586d in ??? (stack-traces)
std.debug.captureStackTrace(null, &trace1);
^
/home/andy/dev/www.ziglang.org/docgen_tmp/stack-traces.zig:17:8: 0x225819 in ??? (stack-traces)
foo();
^
/home/andy/dev/zig/build/lib/zig/std/special/bootstrap.zig:112:22: 0x22528b in ??? (stack-traces)
root.main();
^
/home/andy/dev/zig/build/lib/zig/std/special/bootstrap.zig:43:5: 0x225081 in ??? (stack-traces)
@noInlineCall(posixCallMainAndExit);
^
second one:
/home/andy/dev/www.ziglang.org/docgen_tmp/stack-traces.zig:31:32: 0x22588d in ??? (stack-traces)
std.debug.captureStackTrace(null, &trace2);
^
/home/andy/dev/www.ziglang.org/docgen_tmp/stack-traces.zig:18:8: 0x22581e in ??? (stack-traces)
bar();
^
/home/andy/dev/zig/build/lib/zig/std/special/bootstrap.zig:112:22: 0x22528b in ??? (stack-traces)
root.main();
^
/home/andy/dev/zig/build/lib/zig/std/special/bootstrap.zig:43:5: 0x225081 in ??? (stack-traces)
@noInlineCall(posixCallMainAndExit);
^
This feature is used in the GeneralPurposeDebugAllocator project for helping detect and diagnose memory issues.
Zig 0.4.0 brings breaking changes to the std.mem.Allocator
interface
and API.
Before, allocator implementations had to provide allocFn
,
reallocFn
, and freeFn
.
Now, they must provide only reallocFn
and shrinkFn
.
Reallocating from a zero length slice is allocation, and
shrinking to a zero length slice is freeing.
When the new memory size is less than or equal to the
previous allocation size, reallocFn
now has the option
to return error.OutOfMemory
to indicate that the allocator
would not be able to take advantage of the new size.
For more details see #1306.
I have reproduced the documentation comments here:
pub const Allocator = struct {
pub const Error = error{OutOfMemory};
/// Realloc is used to modify the size or alignment of an existing allocation,
/// as well as to provide the allocator with an opportunity to move an allocation
/// to a better location.
/// When the size/alignment is greater than the previous allocation, this function
/// returns `error.OutOfMemory` when the requested new allocation could not be granted.
/// When the size/alignment is less than or equal to the previous allocation,
/// this function returns `error.OutOfMemory` when the allocator decides the client
/// would be better off keeping the extra alignment/size. Clients will call
/// `shrinkFn` when they require the allocator to track a new alignment/size,
/// and so this function should only return success when the allocator considers
/// the reallocation desirable from the allocator's perspective.
/// As an example, `std.ArrayList` tracks a "capacity", and therefore can handle
/// reallocation failure, even when `new_n` <= `old_mem.len`. A `FixedBufferAllocator`
/// would always return `error.OutOfMemory` for `reallocFn` when the size/alignment
/// is less than or equal to the old allocation, because it cannot reclaim the memory,
/// and thus the `std.ArrayList` would be better off retaining its capacity.
/// When `reallocFn` returns,
/// `return_value[0..min(old_mem.len, new_byte_count)]` must be the same
/// as `old_mem` was when `reallocFn` is called. The bytes of
/// `return_value[old_mem.len..]` have undefined values.
/// The returned slice must have its pointer aligned at least to `new_alignment` bytes.
reallocFn: fn (
self: *Allocator,
/// Guaranteed to be the same as what was returned from most recent call to
/// `reallocFn` or `shrinkFn`.
/// If `old_mem.len == 0` then this is a new allocation and `new_byte_count`
/// is guaranteed to be >= 1.
old_mem: []u8,
/// If `old_mem.len == 0` then this is `undefined`, otherwise:
/// Guaranteed to be the same as what was returned from most recent call to
/// `reallocFn` or `shrinkFn`.
/// Guaranteed to be >= 1.
/// Guaranteed to be a power of 2.
old_alignment: u29,
/// If `new_byte_count` is 0 then this is a free and it is guaranteed that
/// `old_mem.len != 0`.
new_byte_count: usize,
/// Guaranteed to be >= 1.
/// Guaranteed to be a power of 2.
/// Returned slice's pointer must have this alignment.
new_alignment: u29,
) Error![]u8,
/// This function deallocates memory. It must succeed.
shrinkFn: fn (
self: *Allocator,
/// Guaranteed to be the same as what was returned from most recent call to
/// `reallocFn` or `shrinkFn`.
old_mem: []u8,
/// Guaranteed to be the same as what was returned from most recent call to
/// `reallocFn` or `shrinkFn`.
old_alignment: u29,
/// Guaranteed to be less than or equal to `old_mem.len`.
new_byte_count: usize,
/// If `new_byte_count == 0` then this is `undefined`, otherwise:
/// Guaranteed to be less than or equal to `old_alignment`.
new_alignment: u29,
) []u8,
};
This allows allocators and data structures to "negotiate" with each other. For example,
here is std.ArrayList.shrink
:
pub fn shrink(self: *Self, new_len: usize) void {
assert(new_len <= self.len);
self.len = new_len;
self.items = self.allocator.realloc(self.items, new_len) catch |e| switch (e) {
error.OutOfMemory => return, // no problem, capacity is still correct then.
};
}
I am pleased to announce our newest Zig team member, Antoine Vugliano.
He has shown continued dedication and discipline in his contributions to the Zig programming language project. The quality of his work speaks for itself.
I look forward to working with Antoine as we continue to push Zig toward 1.0.0 and beyond.
Thanks to Jeff Fowler for creating sublime-zig-language and for creating the pull request to GitHub to add support for Zig.
Finally, GitHub decided that Zig is popular enough to gain official recognition.
As a fun last step, Marc Tiehuis marked third-party dependencies as vendored, so that we can have this pretty language bar on GitHub:
Now you can discover Zig projects on GitHub via their "trending" web page.
std.os.AtomicFile
std.Mutex
now has a high quality Linux implementation based on
Ulrich Drepper's "Futexes are tricky" paper, Mutex, Take 3.
Thanks to Shawn Landden for the original implementation. #1463ar
.
#1493 #54
posix_spawn
rather than
fork
and execv
. The logic is simplified and the memory usage
is more deterministic.
std.rand.Rand
: better debiased random range implementation. -Josh Wolfeu0
value to any integer type, and the result is always
comptime known to be 0
.std.fmt.formatType
can now format comptime_int -Jimmi Holst Christensen
std.fmt.format
can now format function pointers -Jimmi Holst Christensen
f128
and narrowing casts to f16
from larger float types.f128
support for fabs, isinf, isnan, inf and nan functions.std.json
.std.os.getBaseAddress
for objects and libraries on macOS.
See #1878. This removed the workaround in example/shared_library/mathtest.zig. Along the way
these fixes were made:-target [name]
instead of --target-*
args.
This matches clang's command line interface.builtin.Environ
renamed to builtin.Abi
;
likewise builtin.environ
renamed to
builtin.abi
.zig targets
only shows available targets. #438zig targets
but note they are Tier 4 Support.comptime_int
.popcountdi2
to compiler_rtstd.fmt.formatIntUnsigned
. #1358null
. #2104memcmp
to builtins.std.mem.join
and std.os.path.join
use a slice parameter rather than var args.std.rb
to conform to the
style guide.__modti3
to compiler-rt. #1290std.math.isnan
to use x != x
rather than looking at the bits.comptime_int
in
std.fmt.formatInt
and std.fmt.formatValue
.and
and or
when &&
and ||
are used.std.fmt.parseUnsigned
handle types less than 8 bits wide.std.Mutex
implementation for Windows.Function type mismatches now have an error note which explains in more detail why the function types were incompatible:
test.zig
fn do_the_thing(func: fn (arg: i32) void) void {}
fn bar(arg: bool) void {}
test "fn types" {
do_the_thing(bar);
}
$ zig test test.zig
/home/andy/dev/www.ziglang.org/docgen_tmp/test.zig:4:18: error: expected type 'fn(i32) void', found 'fn(bool) void'
do_the_thing(bar);
^
/home/andy/dev/www.ziglang.org/docgen_tmp/test.zig:4:18: note: parameter 0: 'bool' cannot cast into 'i32'
do_the_thing(bar);
^
std.math.atan2
.comptime
-known to be 0 when destination integer type has 0 bits.
If the destination type is a comptime_int
, treat it as an Implicit Cast. #1568@import("builtin")
and then used to do an implicit cast of the panic
function to this prototype, rather than redoing all the implicit cast logic. #1894 #1895handle_is_ptr
. #991 #1934use
. #1557bigint_append_buf
. All current usages had base 10
and a limb length of 2, hence why we weren't hitting this error in practice.std.json.pushToParent
to work for arrays of Objects--
double-hyphen is now used to end further zig processing
of command line options. All arguments after --
will be passed
on to the executable. eg. --help
will be passed on:
zig run foo.zig -- --help
*const T
to *[1]T
based on a patch
from kristopher tate.for
loop. #1726std.HashMap.remove
returning invalid memory.
std.BufSet
and std.BufMap
where delete would free a still in-use KV and leave
the actually removed KV un-free'd.std.os.File.openWriteNoClobber
and added test coverage.Zig has known bugs.
The first release that will ship with no known bugs will be 1.0.0.
Here is the roadmap from 0.3.0 release notes:
I am sad to report that this list was too ambitious for one release cycle, and while there has been considerable progress, none of these items are complete. And so the roadmap for 0.4.0 is the same.
It's no longer planned for Zig to have Type Based Alias Analysis. However, there are some open research topics on potentially outlawing all kinds of aliasing unless explicitly declared. Subscribe to #1108 for more details.
For now, Zig has a well-defined memory model, and aliasing is always allowed. Note, however, that not all types have a guaranteed in-memory layout.
Linking macOS/COFF files is still in a sad state. The Mach-O code in LLD, Zig's linker, has been barely maintained for several years now. Zig has a fork of LLD in its source tree with a hacky patch to fix linking "Hello World" on macOS.
It's now planned for the Zig project to have its own linker. This is in part due to the lack of Mach-O maintenance, and in part because LLD has no plans to do incremental linking - a feature that I foresee to be necessary to achieve the performance we want to have for large projects in the self-hosted compiler.
For now the self-hosted compiler uses --system-linker-hack
to be able to link successfully, which is a compromise of Zig's promise that it can build
on any target, for any target.
There is also a workaround in the linker phase of Zig for compiler-rt.a and builtin.a. I had to make them object files rather than archive files to avoid crashing LLD.
There has been no progress on the self-hosted compiler in this release cycle. Progress is blocked on the volume of core language changes still happening, as well as no-copy semantics, and Reworking Coroutines.
The package manager will be one of the focus areas of the 0.5.0 release cycle. It depends on networking, which depends on Reworking Coroutines, which depends on no-copy semantics.
There is no progress on coroutines in this release cycle. In fact there has been anti-progress.
Zig 0.4.0 introduces a memory leak to all coroutines.
There is an issue where a coroutine calls the function and it
frees its own stack frame, but then the return value of shrinkFn
is a slice, which is implemented as an sret struct. Writing to
the return pointer causes invalid memory write. We could work
around it by having a global helper function which has a void
return type and calling that instead. But instead this hack will
suffice until I rework coroutines to be non-allocating. Basically
coroutines are not supported right now until they are reworked as
in #1194.
Reworking coroutines is to be a major focus of 0.5.0 as it is blocking networking, the Package Manager, and the Self-Hosted Compiler.
Here is a small selection of interesting proposals that have been accepted, to give you an idea of the upcoming changes to Zig.
Full list of accepted proposals
Special thanks to those who donate monthly. Thanks to you, Zig is not driven by the needs of a business; instead it exists solely to serve the open source community.