Lobsters - 02 Oct 2026

Page 3 of 4

binary format, saving roughly 25% on file size, which eases a bit of pressure on the file system cache

while also simplifying the work the computer needs to do - directly copy bytes from disk rather than

parsing text files. The new zig cache-cat subcommand is available for troubleshooting or

tinkering with files inside a zig-cache directory.

The cache system also now has the capability to explain why a "miss" happened. The public-facing API of

std.Build.Cache has many breaking changes, but outside of compiler tooling, this is

an uncommon API to be used, and all the changes make it harder to misuse.

This change has been observed to speed up cache hits by 5-10% (#36822).

If the cache is poisoned means that the configure logic had side effects, or otherwise did something that could not be tracked by the cache system.

This is not to be confused with whether individual steps may have side effects when being evaluated; it

has to do with the logic inside build.zig itself. For example, a Run step that

prints "hello world" has side effects at make time and therefore does not warrant setting this flag,

while checking for the existence of scdoc at configure time in order to choose the default

value for a configuration option does.

Keeping the cache pure will make zig build faster, bypassing the configurer process when

identical configuration would be generated.

When the cache is poisoned, the maker process will delete the build configuration file upon ingesting it since it cannot be reused.

Ways to poison the cache include calling findProgram, or more directly

std.Build.Graph.poisonCache. A better alternative than cache poisoning is to

explicitly declare the configuration dependencies with these new functions:

- std.Build.dependOnFileContents - indicates that the build.zig logic depends on a particular file's contents.

- std.Build.dependOnFileMetadata - indicates that the build.zig logic depends on a particular file's size, inode, mtime, and contents.

- std.Build.dependOnDirectoryContents - indicates that the build.zig logic depends on a particular directory's entries.

- std.Build.dependOnDirectoryMetadata - indicates that the build.zig logic

depends on a particular directory's last modification date.

Advanced users can override the cache poisoning behavior with a new CLI option:

--cache-poison[=mode] Override configuration caching behavior

pure (default) Avoid false positive cache hits

poisoned Don't cache the configuration

disallowed Panics when cache would be poisoned

ignored A little poison never hurt anybody

Immediately (in the configure phase), searches for an executable on the host that has more than one possible name.

Names are searched in order, observing search prefixes first and then PATH environment variable.

Calling this function poisons the configuration cache, so it is only appropriate when the existence of the program or its output needs to be observed by configuration logic. That's why there is also findProgramLazy now.

Creates an anonymous Step that searches for an executable on the host that

has more than one possible name.

Unlike findProgram, this function does not

poison the configuration cache, however

the result cannot be used in the configuration phase, hence the return type being

LazyPath.

Returns the LazyPath of the found executable. The search only takes place

if the LazyPath will be used by a depending Step.

This API is useful in the following cases:

- The binary is not named the same across all systems (for example "python" vs "python3").

- The binary may be produced by building from source rather than being globally installed and will therefore be possibly found in one of the search prefix paths.

In the Run step, passthru args are all together now, not observable in configure phase whether run args are provided.

--- build.zig

+++ build.zig

@

-if (b.args) |args| {

- run_cmd.addArgs(args);

-}

+run_cmd.addPassthruArgs();

This removes a capability from build scripts since they can no longer observe those arguments. In exchange, it means that when changing those arguments, build scripts no longer must be rebuilt from source.

paths and exclude_paths are now

LazyPath lists. There is a convenience method to create them:

b.pathList.

--- build.zig

+++ build.zig

@

- const fmt_include_paths = &.{ "lib", "src", "test", "tools", "build.zig", "build.zig.zon" };

- const fmt_exclude_paths = &.{ "test/cases", "test/behavior/zon" };

+ const fmt_include_paths = b.pathList(&.{ "lib", "src", "test", "tools", "build.zig", "build.zig.zon" });

+ const fmt_exclude_paths = b.pathList(&.{ "test/cases", "test/behavior/zon" });

Step.Options: add addOptionPathDirectory

Now, when adding an option that is a file path, one must explicitly choose between (#36876):

- addOptionPath (must be a file)

- addOptionPathDirectory (must be a directory)

- addOptionPathUntracked (opt out of dependency tracking)

- Log when lazy dependencies are fetched.

- std.Build.dependency: support lazy dependencies

- Introduce std.Build.dependencyLazy which possibly returns error.LazyDependencyNeeded instead of null, so that you can

use try

- When user build functions return error.LazyDependencyNeeded, build system proceeds to fetch them rather than failing

configuration.

There is no concept of a "build runner" any more; it has been split into: configurer and maker

This use case is now handled by the Build Server Protocol.

All package management functionality has been moved out of the Compiler and into the Build System. This includes the following sub-commands:

- zig build

- zig fetch

- zig init

- zig libc

- zig cache-cat

This means that large parts of what used to be included in the compiler executable are now shipped in source form instead, including:

- package fetching logic

- http client and networking

- TLS (Transport Layer Security) and associated crypto

- git protocol

- xz, gzip, zstd, flate, zip

- parsing, validation, and otherwise dealing with build.zig.zon files

All of this functionality is now compiled in -Osafe

optimization mode rather than -Ofast due to being in the compiler. When hacking on

the build system itself, the environment variable ZIG_DEBUG_CMD=1 may be used to

compile the build system in debug mode instead.

Miscellaneous changes:

- Bug fix: reject path deps that escape the parent package root.

--pkg-path CLI arg and ZIG_LOCAL_PKG_DIR env var are now observed for both

fetch and build commands.

Now zig fetch only fetches into the global cache, just like it used to. However, if

--save (or any variant) is used, then it also fetches into the local package path. When

fetching globally, does not require build.zig to be present. zig build always

fetches locally (in addition to globally).

Notably, this fixes the regressed use case zig fetch .

When fetching by path, the hash is always computed, recompressed tarball is always created, always overwrites any existing global cache entry.

It used to be the case that, when targeting Windows or Wine, artifact args added to Run steps modified PATH based on the set of directories containing the recursive set of DLL dependencies. Now this is only done for argv[0]. The motivation for also doing this for the other command line arguments is unclear, since those DLLs don't need to be loaded in order to execute argv[0].

Now, when --listen=- is passed, the build system serves a protocol that allows connected

clients to monitor and control the build graph as it executes. This is intended to be consumed by

third-party tooling such as IDEs.

Current things you can do:

- Get full access to the entire build graph's static, post-configuration information, such as which build steps are available, which options are set, dependencies, etc. There are a couple things yet to be included, such as the exposed set of module names.

- Get notified when a build step starts and completes, including information about errors and which files were generated.

- Request specific steps to build.

In particular, the separation of maker process and configurer process is a breaking change that prevents the ZLS project from working with 0.17.0. Although some progress was made to restore functionality in this release cycle, Zig team and ZLS team are still working together to enhance the build server protocol further to the point that ZLS can not only restore functionality, but surpass the power and capabilities compared to before.

In the future it is expected for much of Zig's own first-party build system tooling to become a client of the build server protocol, dogfooding it to ensure that third-party tooling enjoys equivalent capabilities (#36497).

It is also planned for the build server to multiplex compiler server protocol for the compilation steps, providing type-system information, refactoring, and other advanced editing capabilities (#615).

The Zig compiler's implementation of incremental compilation - a feature allowing near-instant rebuilds of projects after changing the code - has been significantly improved in Zig 0.17.0. Many bugs have been fixed, and the new ELF Linker introduced in the previous release has gained good support for the feature.

Thanks to these enhancements, it is now possible for most projects targeting

x86_64-linux to take advantage of incremental compilation. To do so, add

the arguments -fincremental --watch to your zig build command (e.g.

zig build -fincremental --watch) - this will cause the Zig build system to

listen for changes to source files, and react to them by performing an incremental rebuild.

For more information on ways to use incremental compilation in your own projects, or to learn more about how this feature works under the hood, consider checking out this blog post by a Zig core team member.

Future releases will continue to focus on improving this feature, including introducing a new

Mach-O linker and self-hosted aarch64 Backend with good support for incremental

compilation; adding support for using incremental compilation without --watch; and

fixing any remaining bugs.

The self-hosted SPIR-V backend is now multi-threaded like the other backends.

Execution modes such as LocalSize and OriginUpperLeft are now derived

from the function's calling convention instead of being set through inline assembly, and the new

spirv_task and spirv_mesh calling conventions add

support for task and mesh shaders (#35676).

Declaring capabilities and extensions in inline assembly with OpCapability and

OpExtension is no longer allowed. They are enabled through target CPU features

instead, i.e. the -mcpu option.

22 bugs were fixed in the SPIR-V backend during this release cycle.

Progress towards this is blocked on Linker enhancements, many of which were completed during this release cycle.

Initial implementation of self-hosted backend for loongarch64 has been contributed (#36418). It is still experimental and not yet usable. There are two ways to contribute to this backend: working on it directly, and contributing to AIR Legalization Features, which helps all unfinished backends reach the finish line quicker.

Zig's WebAssembly backend is now passing 2060/2054 (100%) behavior tests compared to the LLVM backend. However, it is not yet the default when compiling in debug optimization mode due to lack of debug info support (#37032).

This release makes significant progress towards replacing Zig's legacy self-hosted ELF linker with its new implementation introduced in the previous release. Specific enhancements include:

- Full x86_64 support

- Full SPARC64 support

- Partial Loongarch support

- Static library generation

- Shared library generation

- Errors for undefined symbols in executables

- GOT generation

- Copy relocations

- GNU symbol versioning

- DWARF debug information

- Symbol hash table generation

- Mostly-reproducible binaries

- Arbitrary section alignment

- Support for small host file system block sizes

While this linker has not quite reached feature parity with our old self-hosted ELF linker

yet - and so remains disabled by default - it is already capable in practice of building

the vast majority of Zig projects targeting x86_64-linux. This unlocks the ability

to use Incremental Compilation for these projects - like in Zig 0.16.0, the new

linker is enabled by default in this case.

In the next release of Zig, we hope to fully eliminate the legacy ELF linker in favour of this implementation.

COFF support in the linker is enhanced with the following features (#35674):

- Outputing objects (.obj) and archives (.lib)

- Outputing implibs alongside images

- Outputing the TLS and Exports data directories for images

- Consuming objects, archives, and import libraries as inputs

- Only links in objects from archives as required, ie. if they contain a symbol needed to satisfy a reference

- COMDAT rules (enough support for linking compiler_rt and libc, some COMDAT types are not supported yet)

- Supports linking against both -gnu and -msvc libc

- TLS support

- __dllimport support: Indirect calls / loads from the IAT directly

- Detects which entrypoint to choose based on exported symbols

- -gnu: Constructor / destructor support (ie. merge .ctor and .dtor, and set up the __CTOR_LIST__, __DTOR_LIST__ symbols)

- Support for several .drectve arguments (these are required to correctly link msvc libc):

- /INCLUDE: Forcing a symbol to be referenced

- /ALTERNATENAME: Adding symbol aliases

- /MERGE: Section merging. This functionality is also used to direct certain sections into the right

place (like .ctor / .dtor into .rdata)

- /DEFAULTLIB: Adding new inputs

Zig is moving towards snapshot-based testing for its linkers.

Tests are a combination of comparing objdump snapshot output, actually running the artifacts, and checking for linker errors.

zig build -Dlink-snapshot-update causes tests to run in a mode that outputs snapshots

instead of checking against them.

A typical workflow for adding a new test:

- Add the test

- zig-debug build test-link -Dtest-filter=my-test -Dlink-snapshot-update

- This will output a .dmp file for all the snapshot combinations defined in any

verifyObjdump calls.

- A single snapshot intentionally aliases between many targets to reduce noise in the snapshot folder, so snapshot updates are made by whichever test runs first for that snapshot name. If differences between targets do exist, they will be revealed in step 4.

- Inspect the snapshot output for correctness.

- zig-debug build test-link -Dtest-filter=my-test

- This will now run all targets against the newly added snapshots

- If there are now snapshot failures, that means different targets had different snapshot outputs. The

output should be inspected to see if these results are indeed valid differences. If they are, then the

scope parameter should be used to cause -Dlink-snapshot-update to output snapshot

to different filenames, scoped on the diference.

- Re-run -Dlink-snapshot-update to update the new set of snapshots.

The SPIR-V linker has been rewritten (#36828).

It now supports incremental compilation and can link external .spv object files.

Although this release's Build System changes are loosely related to Zig's integrated fuzzer and its interaction with the build system, no changes were made to the fuzzer itself.

We expect to focus on improving the fuzzer in a future release cycle.

Full list of the 329 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, miscompilations, and regressions.

Even with Zig 0.17.x, working on a non-trivial project using Zig may require participating in the development process.

When Zig reaches 1.0.0, Tier 1 support will gain a bug policy as an additional requirement.

We are aware of these notable regressions in 0.17.0:

- #37006: compiler-rt fails to compile for soft float on x86

- #36986: std.debug.simple_panic fails to compile

- #36986: Weak Zig libc symbols cannot be reliably overridden

- #36444: The separation of maker process and configurer process breaks response file use cases for std.Build.Step.Run

- #37050: SPIR-V backend regressions

This release of Zig upgrades to LLVM 22.1.8. This covers Clang (zig cc), libc++, libc++abi, libunwind, and libtsan as well.

In the previous release of Zig, we were forced to disable a key LLVM optimization pass - loop vectorization - to work around a miscompilation which affected the Zig compiler.

Since we first introduced that workaround, a fix has been merged into LLVM's main branch. However, the fix is not available in LLVM 22, the LLVM version used by Zig 0.17.0. Therefore, this workaround remains enabled for now.

Zig 0.18.0 will upgrade to LLVM 23, so will allow us to re-enable this optimization pass.

Zig 0.17.0 distributes musl 1.2.5 plus backported security and portability fixes. Meanwhile, upstream has tagged 1.2.6. Zig 0.18.0 will update to musl 1.2.6.

When targeting musl statically, many functions are now provided by zig libc rather than source files copied from musl. Therefore, if you encounter bugs with musl libc provided by Zig, please respect upstream by reporting them to Zig's issue tracker rather than musl's.

glibc version 2.44 is now available when cross-compiling.

This release includes Linux kernel headers for version 7.2.

This release includes macOS system headers for version 27.0.

Zig 0.17.distributes MinGW-w64 commit 31bd54ab7d5fe03c67ed2bb1a57e531b9c7f8cc4.

However, many functions are now provided by zig libc rather than source files copied from MinGW-w64. Therefore, if you encounter bugs with MinGW-w64 libc provided by Zig, please respect upstream by reporting them to Zig's issue tracker rather than MinGW-w64's.

NetBSD libc version 11.0 is now available when cross-compiling.

OpenBSD libc version 7.9 is now available when cross-compiling.

Zig 0.17.0 continues to distribute WASI libc

commit c89896107d7b57aef69dcadede47409ee4f702ee.

However, many functions are now provided by zig libc rather than source files copied from WASI libc.

Next page | Previous page | More Lobsters | Headlines

Original: https://ziglang.org/download/0.17.0/release-notes.html