0.13.0 Release Notes
Download & Documentation
Zig is a general-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
Zig development is funded via Zig Software Foundation, a 501(c)(3) non-profit organization. Please consider a recurring donation so that we can offer more billable hours to our core team members. This is the most straightforward way to accelerate the project along the Roadmap to 1.0.
This release features 2 months of work: changes from 73 different contributors, spread among 415 commits.
This is a relatively short release cycle, primarily motivated by Toolchain upgrades, such as upgrading to LLVM 18.
A green check mark (✅) indicates the target meets all the requirements for the support tier. The other icons indicate what is preventing the target from reaching the support tier. In other words, the icons are to-do items.
Not only can Zig generate machine code for these targets, but the Standard Library cross-platform abstractions have implementations for these targets.
The CI server automatically tests these targets on every commit to master branch. The ???? icon means this target does not yet have CI test coverage.
The CI server automatically produces pre-built binaries for these targets, on every commit to master, and updates the download page with links. The ???? icon means the download page is missing this target.
These targets have debug info capabilities and therefore produce stack traces on failed assertions.
libc is available for this target even when cross compiling.
All the behavior tests and applicable standard library tests pass for this target. All language features are known to work correctly. Experimental features do not count towards disqualifying an operating system or architecture from Tier 1. The ???? icon means there are known bugs preventing this target from reaching Tier 1.
zig cc, zig c++, and related toolchain commands support this target.
If the Operating System is proprietary then the target is not marked deprecated by the vendor. The ???? icon means the OS is officially deprecated, such as macos/x86.
freestanding Linux 3.16+ macOS 11+ Windows 10+ WASI x86_64 ✅ ✅ ✅ ✅ N/A x86 ✅ #1929 ???? ???? #537 ???? N/A aarch64 ✅ #2443 ???? ✅ #16665 ???? N/A arm ✅ #3174 ???? ???? ???????????? N/A mips ✅ #3345 ???????? N/A N/A N/A riscv64 ✅ #4456 ???? N/A N/A N/A sparc64 ✅ #4931 ???????????? N/A N/A N/A powerpc64 ✅ ???? N/A N/A N/A powerpc ✅ ???? N/A N/A N/A wasm32 ✅ N/A N/A N/A ✅
The Standard Library supports this target, but it is possible that some APIs will give an "Unsupported OS" compile error. One can link with libc or other libraries to fill in the gaps in the standard library. The ???? icon means the standard library is too incomplete to be considered Tier 2 worthy.
These targets are known to work, but may not be automatically tested, so there are occasional regressions. ???? means that nobody has really looked into this target so whether or not it works is unknown.
Some tests may be disabled for these targets as we work toward Tier 1 Support.
The standard library has little to no knowledge of the existence of this target.
If this target is provided by LLVM, LLVM has the target enabled by default.
These targets are not frequently tested; one will likely need to contribute to Zig in order to build for these targets.
The Zig compiler might need to be updated with a few things such as what sizes are the C integer types C ABI calling convention for this target start code and default panic handler
zig targets is guaranteed to include this target.
freestanding emscripten wasm32 Tier 1 ✅
Support for these targets is entirely experimental.
If this target is provided by LLVM, LLVM may have the target as an experimental target, which means that you need to use Zig-provided binaries for the target to be available, or build LLVM from source with special configure flags. zig targets will display the target if it is available.
will display the target if it is available. This target may be considered deprecated by an official party, in which case this target will remain forever stuck in Tier 4.
This target may only support -femit-asm and cannot emit object files, in which case -fno-emit-bin is enabled by default and cannot be overridden.
Tier 4 targets:
avr
riscv32
xcore
nvptx
msp430
r600
arc
tce
le
amdil
hsail
spir
kalimba
shave
renderscript
32-bit x86 macOS, 32-bit ARM macOS, powerpc32 and powerpc64 macOS, because Apple has officially dropped support for them.
std.process.Child: Mitigate arbitrary command execution vulnerability on Windows (BatBadBut) (#19698)
See the pull request description for the details
This removes the two original implementations in favour of the single generic one based on the Algorithm type. Previously we had three, very similar implementations which was somewhat confusing when knowing what one should actually be used.
The previous polynomials all have equivalent variants available when using the Algorithm type.
The Koopman polynomial did not have an exact equivalent so one has been added to the catalog.txt file. This does mean we have patched this file but this will be clear if updated in future and tests will catch this.
This is a breaking change for direct users of the old polynomial api. Specifically when using a custom or non-standard polynomial (the Crc32 alias will continue to work). There are clear compile errors indicating what is required in order to retain existing functionality, this may require a small code-change from the user.
const hash = Crc32WithPoly(.Castagnoli); const hash = Crc(.Crc32Iscsi);
Custom polynomials require a bit more interaction and will require a user to define their own Algorithm type. If used note that the previous implementation expected a pre-reflected polynomial and used the following parameters:
.{ .polynomial = 0x741b8cd7 , .initial = 0xffffffff , .reflect_input = true , .reflect_output = true , .xor_output = 0xffffffff , });
Loose performance measurements below. Table sizes are indicated to the right. Nothing new, helps rationalise the overlap that was present.
crc32-slicing-by-8 # 8K of tables iterative: 3074 MiB/s [2d191d9400000000] small keys: 32B 4650 MiB/s 152387950 Hashes/s [20024c446a99a300] crc32-half-byte-lookup # 64b of tables iterative: 281 MiB/s [2d191d9400000000] small keys: 32B 389 MiB/s 12751954 Hashes/s [20024c446a99a300] crc32 # 1K of tables iterative: 3077 MiB/s [2d191d9400000000] small keys: 32B 4660 MiB/s 152709182 Hashes/s [20024c446a99a300]
ComptimeStringMap is renamed to StaticStringMap, accepts only a single type parameter, and returns a known struct type instead of an anonymous struct. Initial motivation for these changes was to reduce the 'very long type names' issue described in #19682.
This breaks the previous API. Users will now need to write:
const map = std.StaticStringMap(T).initComptime(kvs_list);
More details:
Moved kvs_list param from type param to an initComptime() param
param from type param to an param New public methods: keys() , values() helpers init(allocator) , deinit(allocator) for runtime data getLongestPrefix(str) , getLongestPrefixIndex(str) - i'm not sure these belong but have left in for now incase they are deemed useful
Performance notes: Some benchmarking results are here: travisstaloch/comptime-string-map-revised#1 Speedup from reducing the size of the struct from 48 to 32 bytes and thus use u32s instead of usize for all length fields Speedup from storing KVs as a struct of arrays Latest benchmark shows these wall_time improvements for debug/safe/small/fast builds: -6.6% / -10.2% / -19.1% / -8.9%. Full output in link above.
This is a breaking change that aligns the PriorityQueue API to the ArrayList API.
Before, PriorityQueue stored the full allocated slice in the items field, and length in a separate len field. This was inconsistent with ArrayList and led to the mistake of accessing undefined memory.
Now, the items field points to only the valid items in the queue, and the extra unused capacity is stored in a separate cap field.
#19960
The old name has been deprecated for several releases now.
Upgrade guide:
std.ChildProcess
↓
std.process.Child
Some other functions are also moved from std.ChildProcess to std.process namespace.
In the future there will be even more breaking changes. For example, instead of creating a Child and then setting fields on it and then calling spawn, there will be std.process.spawn which takes an "options" parameter and then returns the Child, which is an object that lasts only from spawn until termination. This is a practice that we have been moving more towards in Zig, which is to have types designed to have minimal lifetimes and minimal states with undefined fields.
Primarily, this is a breaking change to the Standard Library. However, the Compiler and Build System both lean heavily on this API and are thereby affected.
simple asciinema demo [demo source]
demo: building a music player with zig build [source code]
Performance impact: insignificant [source]
Upgrade Guide:
Pass around std.Progress.Node instead of *std.Progress.Node (no longer a pointer).
instead of (no longer a pointer). Remove calls to node.activate() . Those are not needed anymore.
. Those are not needed anymore. It's now safe to pass short-lived strings to Node.start since the data will be copied.
since the data will be copied. It is illegal to initialize std.Progress more than once. Do that in main() and nowhere else.
more than once. Do that in main() and nowhere else. Use std.debug.lockStdErr and std.debug.unlockStdErr before writing to stderr to integrate properly with std.Progress ( std.debug.print already does this for you).
var progress = std.Progress{ ... }; const root_node = progress.start( "Test" , test_fn_list.len);
↓
const root_node = std.Progress.start(.{ .root_name = "Test" , .estimated_total_items = test_fn_list.len, });
All the options to start are optional.
Finally, when spawning a child process, populate the progress_node field first:
child.progress_node = node; try child.spawn();
The previous implementation of std.Progress had the design limitation that it could not assume ownership of the terminal. This meant that it had to play nicely with sub-processes purely via what was printed to the terminal, and it had to play nicely with progress-unaware stderr writes to the terminal. It also was forbidden from installing a SIGWINCH handler, or running ioctl to find out the rows and cols of the terminal.
The new implementation is designed around the idea that a single process will be the sole owner of the terminal, and all other progress reports will be communicated back to that process. With this change in the requirements, it becomes possible to make a much more useful progress bar.
This creates a standard "Zig Progress Protocol" and uses it so that the same std.Progress API works both when an application is the main owner of a terminal, and when an application is a child process. In the latter case, progress information is communicated semantically over a pipe to the parent process.
The file descriptor is given in the ZIG_PROGRESS environment variable. std.process.Child integrates with this, so attaching a child's progress subtree in a parent process is as easy as setting the child.progress_node field before calling spawn .
In order to avoid performance penalty for using this API, the Node.start and Node.end APIs are thread-safe, lock-free, infallible, and do minimal amount of memory loads and stores. In order to accomplish this, a statically allocated buffer of Node storage is used - one array for parents, and one array for the rest of the data. Children are not stored. The statically allocated buffer is used for a bespoke Node allocator implementation. A static buffer is sufficient because we can set an upper bound on supported terminal width and height. If the terminal size exceeds this, the progress bar output will be truncated regardless.
A separate thread periodically refreshes the terminal on a timer. This progress update thread iterates over the entire preallocated parents array, looking for used nodes. This is efficient because the parents array is only 200 8-bit integers, or about 4 cache lines. When iterating, this thread "serializes" the data into a separate preallocated array by atomically loading from the shared data into data that is only touched by a single thread - the progress update thread. It then looks for nodes that are marked with a file descriptor that is a pipe to a child process. Such nodes are replaced during the serialization process with the data from reading from the pipe. The data can be memcpy'd into place except for the parents array which needs to be relocated. Once this serialization process is complete, there are two paths, one for a child process, and one for the root process that owns the terminal.
The root process that owns the terminal scans the serialized data, computing children and sibling pointers. The canonical data only stores parents, so this is where the tree structure is computed. Then the tree is walked, appending to a static buffer that will be sent to the terminal with a single write() syscall. During this process, the detected rows and cols of the terminal are respected. If the user resizes the terminal, it will cause a SIGWINCH which signals the update thread to wake up and redraw with the new rows and cols.
A child process, instead of drawing to the terminal, takes the same serialized data and sends it across a pipe. The pipe is in non-blocking mode, so if it fills up, the child drops the message; a future update will contain the new progress information. Likewise when the parent reads from the pipe, it discards all messages in the buffer except for the last one. If there are no messages in the pipe, the parent uses the data from the last update.
Andrew Kelley's blog post on the topic
Upgrade guide:
.{ .iov_base = message.ptr, .iov_len = message.len },
↓
.{ .base = message.ptr, .len = message.len },
The docs for setting stdio to "inherit" say:
Causes the Run step to be considered to have side-effects, and therefore always execute when it appears in the build graph. It also means that this step will obtain a global lock to prevent other steps from running in the meantime. The step will fail if the subprocess crashes or returns a non-zero exit code.
The implementation of this lock was missing but is now implemented, ensuring that only one process which owns stdout/stderr is running at a time.
Windows does not support RPATH and only searches for DLLs in a small number of predetermined paths by default, with one of them being the directory from which the application loaded.
Currently, if you build an executable and a DLL, link them and install them with the default settings, the exe will go in bin/ and the DLL in lib/ , which makes the exe unable to find the DLL at runtime without manually adding lib/ to the PATH environment variable.
Installing both executables and DLLs to bin/ by default helps ensure that the executable can find any DLL artifacts it has linked to. DLL import libraries are still installed to lib/ .
These defaults match CMake's behavior.
Illustrative example:
exe.root_module.linkLibrary(sdl3_lib); b.installArtifact(exe); b.installArtifact(sdl3_lib);
zig-out/ ├───bin/ │ ├───example.exe │ ├───example.pdb │ ├───SDL3.dll │ └───SDL3.pdb ├───include/ │ └───SDL3/ │ ├───SDL.h │ └───<omitted> └───lib/ └───SDL3.lib
The actual zig objcopy does not accept keeping multiple sections. If you pass multiple -j .section arguments to zig objcopy , it will only respect the last one passed.
The build system API now uses a type that reflects this.
Detecting the NO_COLOR environment variable and disabling color output if set is a standard practice respected by most CLI tools.
When it comes to the inverse task of forcing color output even when not writing to a terminal, there exist two standards, CLICOLOR_FORCE and FORCE_COLOR . Neither of these two standards come even close to being as ubiquitous as NO_COLOR , but they both have some precedence and are respected by a handful of CLI tools.
Prior to e45d24c0de29eb6668e56ea927e15505674833a6, Zig used the ZIG_DEBUG_COLOR env variable to force color output, but that commit changed it to YES_COLOR .
YES_COLOR seemingly has next to no precedent in existing software (a search for /(?-i)\bYES_COLOR\b/ on GitHub returned 142 files).
Instead of throwing a third standard into the mix and making it even harder for users to manage colored output in CLI tooling, it makes more sense to instead tag along with one of CLICOLOR_FORCE or FORCE_COLOR .
CLICOLOR_FORCE is chosen over FORCE_COLOR for the following reasons:
https://bixense.com/clicolors/, which attempts to standardize CLICOLOR_FORCE , was created in 2015. The corresponding https://force-color.org/ for FORCE_COLOR was created in 2023.
, was created in 2015. The corresponding https://force-color.org/ for was created in 2023. CLICOLOR_FORCE appears to have been introduced by ls in FreeBSD 4.1.1 in 2000. FORCE_COLOR appears to have been introduced by the chalk JavaScript library in 2015.
appears to have been introduced by in FreeBSD 4.1.1 in 2000. appears to have been introduced by the chalk JavaScript library in 2015. CLICOLOR_FORCE is supported by CMake and Ninja.
is supported by CMake and Ninja. While a search for /(?-i)\bCLICOLOR_FORCE\b/ returns 28.9k files and /(?-i)\bFORCE_COLOR\b/ 39k files, the former seems more common with software written in C/C++ while the latter seems more tied to Node.js and Python ecosystems.
In a better universe, someone would have established a convention like CLICOLOR=ON or CLICOLOR=OFF way back in time so that we wouldn't have to juggle multiple environemnt variables, but that ship has sailed.
CLICOLOR_FORCE is also added to the output of zig env .
loongarch64 support added. It still can't build hello world because LLVM didn't implement fp_to_fp16 for that target yet.
This plays nicely with more tooling. For example, text editors will typically exclude this directory from "find in files" features. It communicates to new users of Zig that these files are ephemeral. I apologize for not getting this right the first time.
zig-out is unchanged.
Full list of the 43 bug reports closed during this release cycle.
Many bugs were both introduced and resolved within this release cycle. Most bug fixes are omitted from these release notes for the sake of brevity.
Zig has known bugs and even some miscompilations.
Zig is immature. Even with Zig 0.13.0, working on a non-trivial project using Zig will likely require participating in the development process.
When Zig reaches 1.0.0, Tier 1 Support will gain a bug policy as an additional requirement.
This release of Zig upgrades to LLVM 18.1.7.
This was the primary motivation for tagging the 0.13.0 release.
Zig ships with the source code to musl. When the musl C ABI is selected, Zig builds static musl from source for the selected target. Zig also supports targeting dynamically linked musl which is useful for Linux distributions that use it as their system libc, such as Alpine Linux.
This release upgrades from v1.2.4 to v1.2.5.
glibc version 2.39 are now available when cross-compiling.
The major theme of the 0.14.0 release cycle will be compilation speed.
Some upcoming milestones we will be working towards in the 0.14.0 release cycle:
Making the x86 Backend the default backend for debug mode.
Linker support for COFF. Eliminate dependency on LLD.
Enabling incremental compilation for fast rebuilds.
Introduce Concurrency to semantic analysis to further increase compilation speed.
The idea here is that prioritizing faster compilation will increase development velocity on the Compiler itself, leading to more bugs fixed and features completed in the following release cycles.
It also could potentially lead to language changes that unblock fast compilation.
Here are all the people who landed at least one contribution into this release:
Andrew Kelley
David Rubin
Jakub Konka
Jacob Young
Ryan Liptak
Veikka Tuominen
Eric Joldasov
Wooster
Frank Denis
Hampus Fröjdholm
Matthew Lugg
Christofer Nolander
Motiejus Jakštys
Pat Tullmann
clickingbuttons
Carl Åstholm
Eric Joldasov
Garfield Lee
Linus Groh
Marc Tiehuis
Markus F.X.J. Oberhumer
Sean
Travis Staloch
antlilja
expikr
february cozzocrea
190n
Abhinav Gupta
Alain Greppin
Alex Kladov
Alexandre Janon
Antonio Gomes
Ben Crist
Casey Banner
Dominic
Evan Haas
George Thayamkery
Georgijs
Igor Anić
IntegratedQuantum
Jared Baur
Jiacai Liu
Jonathan Marler
Jordan Yates
Julian
Kang Seonghoon
Karl Bohlmark
Lucas Santos
Maciej 'vesim' Kuliński
Manlio Perillo
Marco F
Meghan Denny
Michael Dusan
Nameless
Pavel Verigo
Pyrolistical
Pyry Kovanen
Ridai Govinda Pombo
Ronald Chen
Simon Brown
T. M
Tim Culverhouse
Vahur Sinijärv
Wes Koerber
Xavier Bouchoux
YANG Xudong
daurnimator
freakmangd
koenditor
orvit
poypoyan
reokodoku
zhylmzr
Special thanks to those who sponsor Zig. Because of recurring donations, Zig is driven by the open source community, rather than the goal of making profit. In particular, these fine folks sponsor Zig for $50/month or more: