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 8 months of work: changes from 268 different contributors, spread among 3688 commits.
In the past, these release notes have been extremely long, attempting to take note of all enhancements that occurred during the release cycle. In the interest of not overwhelming the reader as well as the maintainers creating these notes, this document is abridged. Many changes, including API breaking changes, are not mentioned here.
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. If you find any wrong data here please submit a pull request!
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 | ✅ |
free standing | Linux 3.16+ | macOS 11+ | Windows 10+ | FreeBSD 12.0+ | NetBSD 8.0+ | Dragon FlyBSD 5.8+ | OpenBSD 7.3+ | UEFI | |
---|---|---|---|---|---|---|---|---|---|
x86_64 | Tier 1 | Tier 1 | Tier 1 | Tier 1 | ✅ | ✅ | ✅ | ✅ | ✅ |
x86 | Tier 1 | ✅ | 💀 | ✅ | 🔍 | 🔍 | N/A | 🔍 | ✅ |
aarch64 | Tier 1 | ✅ | Tier 1 | ✅ | 🔍 | 🔍 | N/A | 🔍 | 🔍 |
arm | Tier 1 | ✅ | 💀 | 🔍 | 🔍 | 🔍 | N/A | 🔍 | 🔍 |
mips64 | ✅ | ✅ | N/A | N/A | 🔍 | 🔍 | N/A | 🔍 | N/A |
mips | Tier 1 | ✅ | N/A | N/A | 🔍 | 🔍 | N/A | 🔍 | N/A |
powerpc64 | Tier 1 | ✅ | 💀 | N/A | 🔍 | 🔍 | N/A | 🔍 | N/A |
powerpc | Tier 1 | ✅ | 💀 | N/A | 🔍 | 🔍 | N/A | 🔍 | N/A |
riscv64 | Tier 1 | ✅ | N/A | N/A | 🔍 | 🔍 | N/A | 🔍 | 🔍 |
sparc64 | Tier 1 | ✅ | N/A | N/A | 🔍 | 🔍 | N/A | 🔍 | N/A |
zig targets
is guaranteed to include this target.freestanding | Linux 3.16+ | Windows 10+ | FreeBSD 12.0+ | NetBSD 8.0+ | UEFI | |
---|---|---|---|---|---|---|
x86_64 | Tier 1 | Tier 1 | Tier 1 | Tier 2 | Tier 2 | Tier 2 |
x86 | Tier 1 | Tier 2 | Tier 2 | ✅ | ✅ | Tier 2 |
aarch64 | Tier 1 | Tier 2 | Tier 2 | ✅ | ✅ | ✅ |
arm | Tier 1 | Tier 2 | ✅ | ✅ | ✅ | ✅ |
mips64 | Tier 2 | Tier 2 | N/A | ✅ | ✅ | N/A |
mips | Tier 1 | Tier 2 | N/A | ✅ | ✅ | N/A |
riscv64 | Tier 1 | Tier 2 | N/A | ✅ | ✅ | ✅ |
powerpc32 | Tier 2 | Tier 2 | N/A | ✅ | ✅ | N/A |
powerpc64 | Tier 2 | Tier 2 | N/A | ✅ | ✅ | N/A |
bpf | ✅ | ✅ | N/A | ✅ | ✅ | N/A |
hexagon | ✅ | ✅ | N/A | ✅ | ✅ | N/A |
amdgcn | ✅ | ✅ | N/A | ✅ | ✅ | N/A |
sparc | ✅ | ✅ | N/A | ✅ | ✅ | N/A |
s390x | ✅ | ✅ | N/A | ✅ | ✅ | N/A |
lanai | ✅ | ✅ | N/A | ✅ | ✅ | N/A |
csky | ✅ | ✅ | N/A | ✅ | ✅ | N/A |
freestanding | emscripten | |
---|---|---|
wasm32 | Tier 1 | ✅ |
zig targets
will display the target if it is available.-femit-asm
and cannot emit
object files, in which case -fno-emit-bin
is enabled by
default and cannot be overridden.Tier 4 targets:
This release deletes the previous (experimental) Autodoc implementation and replaces it with a new (not experimental!) one.
The old implementation looked like this:
5987 src/Autodoc.zig 435 src/autodoc/render_source.zig 10270 lib/docs/commonmark.js 1245 lib/docs/index.html 5242 lib/docs/main.js 2146 lib/docs/ziglexer.js 25325 total
After compilation (sizes are for standard library documentation):
272K commonmark.js 3.8M data-astNodes.js 360K data-calls.js 767K data-comptimeExprs.js 2.2M data-decls.js 896K data-exprs.js 13K data-files.js 45 data-guideSections.js 129 data-modules.js 15 data-rootMod.js 294 data-typeKinds.js 3.2M data-types.js 38K index.html 158K main.js 36M src/ (470 .zig.html files) 78K ziglexer.js
Total output size: 47M (5.7M gzipped)
src/Autodoc.zig
processed ZIR code, outputting JSON data for a web application to consume. This resulted in a lot of code ineffectively trying to reconstruct the AST from no-longer-available data.
lib/docs/commonmark.js
was a third-party markdown implementation that supported too many features; for example I do not want it to be possible to have HTML tags in doc comments, because that would make source code uglier. Only markdown that looks good both as source and rendered should be allowed.
lib/docs/ziglexer.js
was an implementation of Zig language tokenization in JavaScript, despite Zig already exposing its own tokenizer in the standard library. When I saw this added to the zig project, a little part of me died inside.
src/autodoc/render_source.zig
was a tool that converted .zig files to a syntax-highlighted but non-interactive .zig.html files.
The new implementation looks like this:
942 lib/docs/main.js 403 lib/docs/index.html 933 lib/docs/wasm/markdown.zig 226 lib/docs/wasm/Decl.zig 1500 lib/docs/wasm/markdown/Parser.zig 254 lib/docs/wasm/markdown/renderer.zig 192 lib/docs/wasm/markdown/Document.zig 941 lib/docs/wasm/main.zig 1038 lib/docs/wasm/Walk.zig 6630 total
After compilation (sizes are for standard library documentation):
12K index.html 32K main.js 192K main.wasm 12M sources.tar
Total output size: 12M (2.3M gzipped)
As you can see, it is both dramatically simpler in terms of implementation as well as build artifacts. Now there are exactly 4 files instead of many, with a 4x reduction in total file size of the generated web app.
However, not only is it simpler, it is actually more powerful than the old system, because instead of processing ZIR, this system processes the source files directly, meaning it has 100% of the information and never needs to piece anything together backwards.
This strategy uses a WebAssembly module written in Zig. This allows it to reuse components from the compiler, such as the tokenizer, parser, and other utilities for operating on Zig code.
The sources.tar file, after being decompressed by the HTTP layer, is fed directly into the wasm module's memory. The tar file is parsed using std.tar and source files are parsed in place, with some additional computations added to hash tables on the side.
There is room for introducing worker threads to speed up the parsing, although single-threaded it is already so fast that it does not seem necessary.
In Zig 0.11.0, a Zig installation comes with a docs/std/
directory that contains those 47M of output artifacts mentioned above.
This rewrite removed those artifacts from Zig installations, instead
offering the zig std
command, which hosts std lib autodocs and
spawns a browser window to view them. When this command is activated,
lib/compiler/std-docs.zig
is compiled from source to perform
this operation.
The HTTP server creates the requested files on the fly, including
rebuilding main.wasm
if any of its source files changed, and constructing
sources.tar
, meaning that any source changes to the documented files,
or to the autodoc system itself are immediately reflected when
viewing docs. Prefixing the URL with /debug
results in a debug
build of the WebAssembly module.
This means contributors can test changes to Zig standard library documentation, as well as autodocs functionality, by pressing refresh in their browser window, using a only binary distribution of Zig.
In total, this reduced the Zig installation size from 317M to 268M (-15%).
A ReleaseSmall build of the compiler shrinks from 10M to 9.8M (-1%).
Autodocs generation is now done properly as part of the pipeline of the compiler rather than tacked on at the end. It also no longer has any dependencies on other parts of the pipeline.
This is how long it now takes to generate standard library documentation:
Benchmark 1 (3 runs): old/zig test /home/andy/dev/zig/lib/std/std.zig -fno-emit-bin -femit-docs=docs measurement mean ± σ min … max outliers delta wall_time 13.3s ± 405ms 12.8s … 13.6s 0 ( 0%) 0% peak_rss 1.08GB ± 463KB 1.08GB … 1.08GB 0 ( 0%) 0% cpu_cycles 54.8G ± 878M 54.3G … 55.8G 0 ( 0%) 0% instructions 106G ± 313K 106G … 106G 0 ( 0%) 0% cache_references 2.11G ± 35.4M 2.07G … 2.14G 0 ( 0%) 0% cache_misses 41.3M ± 455K 40.8M … 41.7M 0 ( 0%) 0% branch_misses 116M ± 67.8K 116M … 116M 0 ( 0%) 0% Benchmark 2 (197 runs): new/zig build-obj -fno-emit-bin -femit-docs=docs ../lib/std/std.zig measurement mean ± σ min … max outliers delta wall_time 24.6ms ± 1.03ms 22.8ms … 28.3ms 4 ( 2%) ⚡- 99.8% ± 0.3% peak_rss 87.3MB ± 60.6KB 87.2MB … 87.4MB 0 ( 0%) ⚡- 91.9% ± 0.0% cpu_cycles 38.4M ± 903K 37.4M … 46.1M 13 ( 7%) ⚡- 99.9% ± 0.2% instructions 39.7M ± 12.4K 39.7M … 39.8M 0 ( 0%) ⚡-100.0% ± 0.0% cache_references 2.65M ± 89.1K 2.54M … 3.43M 3 ( 2%) ⚡- 99.9% ± 0.2% cache_misses 197K ± 5.71K 186K … 209K 0 ( 0%) ⚡- 99.5% ± 0.1% branch_misses 184K ± 1.97K 178K … 190K 6 ( 3%) ⚡- 99.8% ± 0.0%
It used to take upwards of 13 seconds. Now it takes 25ms.
This stems from the fact that with full source files we have all the information, and can write more robust code to look up identifiers from the context they occur in.
Press u
to go to source code for any declaration:
The links take you to the API page for that specific link by changing the location hash.
Merged error sets are detected:
Errors that come from other declarations are linked:
Errors are also shown on function view:
Previous implementation guesses wrong on the type of options
as well as DynLib
.
The previous implementation implemented scroll history in JavaScript, which is impossible to do correctly. The new system makes careful use of the 'popstate' event combined with the history API to scroll to the top of the window only when the user navigates to a new link - respecting the browser's saved scroll history in all other cases.
For more details see the commit diff.
Zig 0.12.0 introduces a new compile error which is emitted when a local variable is declared as
a var
, but the compiler can infer that const
would suffice.
As indicated by the error message, the solution is simple: use const
instead
where applicable.
Zig 0.12.0 contains several enhancements to Result Location Semantics (RLS).
This release implements forwarding of result types through the address-of operator (&
). This allows
syntactic constructs which rely on result types, such as anonymous initializations .{ ... }
and casting builtins like @intCast
, to function correctly in the presence of the
address-of operator:
In addition, Zig 0.12.0 removes the ability for result locations to propagate through @as
and explicitly-typed aggregate initializations T{ ... }
. This restriction is in place to
simplify the language design: previous releases contained several bugs relating to incorrect casting of result pointers.
Zig 0.12.0 introduces a new syntax to allow destructuring indexable aggregates: that is, tuples, vectors, and arrays. Writing a sequence of lvalues or local variable declarations on the left-hand side of the assignment will attempt to destructure the value specified on the right-hand side:
Slices cannot be directly destructured. To destructure values from a slice, convert it to an array by slicing
with comptime-known bounds, such as slice[0..3].*
.
In Zig, struct
, enum
, union
, and opaque
types are special. They
do not use structural equivalence, like tuples and arrays do; instead, they create distinct types. These types have
namespaces, and thus may contain declarations. For this reason, they can be referred to collectively as "namespace
types".
In 0.11.0, every time a declaration of such a type was semantically analyzed, a new type was created. Equivalence of
generic types was handled via memoization of comptime function calls; i.e.
std.ArrayList(u8) == std.ArrayList(u8)
held because the ArrayList
function was only called
once, and its results memoized.
In 0.12.0, this has changed. Namespace types are now deduplicated based on two factors: their source location, and their captures.
The "captures" of a type refers to the set of comptime-known types and values which it closes over. In other words,
it is the set of values referenced within the type but declared outside of it. For instance, the
comptime T: type
parameter of std.ArrayList
is captured by the type it returns. If two
namespace types are declared by the same piece of code and have the same captures, they are now considered to be
precisely the same type.
Note that the compiler will still memoize comptime calls: that hasn't changed. However, this memoization no longer has a meaningful impact on language semantics.
It is unlikely that this change will cause breakage in existing code. The most likely scenario where it could is something like the following:
In Zig 0.11.0, this code would create two distinct types, because the calls to MakeOpaque
are distinct
and thus the opaque
declaration was analyzed separately for each call. In Zig 0.12.0, these types are
identical (A == B
), because while the function is called twice, the declaration does not capture any
value.
This code can be fixed by forcing the type declaration to capture n
:
Since n
is referenced within the opaque
declaration, this code creates two distinct types.
Zig 0.12.0 overhauls the compiler's internal representation of comptime memory, and more specifically
comptime-mutable memory (i.e. comptime var
). This overhaul comes with some user-facing
changes in the form of new restrictions on what you can do with a comptime var
.
The first, and most significant, new rule is that a pointer to a comptime var
is never
allowed to become runtime-known. For instance, consider the following snippet:
In previous versions of Zig, this test passed as you might expect. In Zig 0.12.0, it emits a compile error,
because the assignment to ptr
makes the value &x
- which is a pointer to a
comptime var
- runtime-known.
Such pointers can also become runtime-known by, for instance, being passed to a function called at runtime:
This test also emits a compile error in Zig 0.12.0. The call to load
occurs at runtime, and its
ptr
parameter is not marked comptime
, so ptr
is runtime-known
within the body of load
. This means the call to load
makes the pointer &x
runtime-known, hence the compile error.
This restriction was put in place to fix some soundness bugs. When a pointer to a comptime var
becomes runtime-known, mutations to it become invalid since the pointed-to data becomes constant, but the type system
fails to reflect this, leading to the potential for runtime segmentation faults in what appears to be valid code. In
addition, the value you read from such a pointer at runtime would be its "final" comptime value, which was an
unintuitive behavior. Thus, these pointers can no longer be runtime-known.
The second new restriction is that a pointer to a comptime var
is never allowed to be contained
within the resolved value of a global declaration. For instance, consider the following snippet:
Here, ptr
is a global declaration whose value is a pointer to a comptime var
.
This declaration was permitted in Zig 0.11.0, but raises a compile error in Zig 0.12.0. The same rule applies in
more complex cases, such as when the pointer is contained within a struct field:
This code raises the same compile error as the previous example. This restriction has been put in place primarily to aid the implementation of incremental compilation in the Zig compiler, which depends on the fact that analysis of global declarations is order-independent, and the dependencies between declarations can be easily modeled.
The most common way for this to manifest as a compile error in existing code is if a function constructs a slice at comptime which is then used at runtime. For instance, consider the following snippet:
A call to getName
returns a slice whose ptr
field is a pointer to a
comptime var
. This means the value cannot be used at runtime, nor can it appear in the value
of a global declaration. This code can be fixed by promoting the computed data to a const
after filling the buffer:
Like in previous versions of Zig, comptime-known const
s have infinite lifetime, and the
restrictions discussed here do not apply to them. Therefore, this code functions as expected.
Another possible failure mode is in code which used the old semantics to create global mutable comptime state. For instance, the following snippet attempts to create a global comptime counter:
This code emits a compile error in Zig 0.12.0. This use case is not and will not be supported by Zig: any mutable comptime state must be represented locally.
The first argument is removed in favor of using the result type.
Migration guide:
↓or
depending on what parent pointer alignment the compiler is able to prove.
The second form is more portable, since it's possible for the
@alignCast
to be needed for some targets but not others.
Zig 0.11.0 allowed function types to specify an alignment. This is disallowed in Zig 0.12.0, because is it a property of function declarations and pointers, not of function types.
Previous releases of Zig included an @errSetCast
builtin which performed a safety-checked
cast from one error set to another, potentially smaller, one. In Zig 0.12.0, this builtin is replaced with
@errorCast
. Previous uses will continue to work, but in addition, this new builtin can cast
the error set of an error union:
Previous releases of Zig included the @fabs
builtin. This has been replaced with a new
@abs
builtin, which is able to operate on integers as well as floats:
On Windows, the command line arguments of a program are a single WTF-16 encoded string and it's up to the program to split it into an array of strings. In C/C++, the entry point of the C runtime takes care of splitting the command line and passing argc/argv to the main function.
Previously, std.process.argsAlloc
and related functions did not fully match the parsing behavior of
the C runtime and would split the command line incorrectly in some cases. For example, when encountering consecutive
double quotes inside a quoted block like "foo""bar"
, these functions would produce foobar
instead of the expected foo"bar
This release updates Zig's command line splitting to match the post-2008 splitting behavior of the C runtime, which ensures consistent behavior between Zig and modern C/C++ programs on Windows. Additionally, the suggested mitigation for BatBadBut relies on the post-2008 C runtime splitting behavior for roundtripping of the arguments given to cmd.exe.
The BadBatBut mitigation did not make the 0.12.0 release cutoff.
Previous versions of Zig allowed applications to override the POSIX API layer of the standard library. This release intentionally removes this ability, with no migration path offered.
This was a mistake from day one. This is the wrong abstraction layer to do this in.
The alternate plan for this is to make all I/O operations require an IO interface parameter, similar to how allocations require an Allocator interface parameter today.
Such a plan is not yet implemented, so applications which require this functionality must maintain a fork of the standard library until then.
Migration guide:
↓Generally, one should prefer to use the higher-level cross-platform abstractions rather than reaching
into the POSIX API layer. For example, std.process.exit
is more portable than
std.posix.exit
. You should generally expect the API inside std.posix
to be available on a given OS when the OS implements that corresponding POSIX functionality.
Zig 0.12.0 replaces the previous errol floating point formatting algorithm with one based on Ryu, a modern algorithm for converting IEEE-754 floating-point numbers to decimal strings.
The improvements this brings are:
Behavior Differences:
errol: 1e+02 ryu: 1e2
errol: 2.0e+00 ryu: 2e0
# Ryu 3.1234567891011121314151617181920212E0 :f128 3.1234567891011121314E0 :f80 3.1234567891011121314E0 :c_longdouble 3.123456789101112E0 :f64 3.1234567E0 :f32 3.123E0 :f16 ## Errol 3.123456789101112e+00 :f128 3.123456789101112e+00 :f80 3.123456789101112e+00 :c_longdouble 3.123456789101112e+00 :f64 3.12345671e+00 :f32 3.123046875e+00 :f16
# bits: 141333 # precision: 3 # std_shortest: 1.98049715e-40 # ryu_shortest: 1.9805e-40 # type: f32 | | std_dec: 0.000 | ryu_dec: 0.000 | | std_exp: 1.980e-40 | ryu_exp: 1.981e-40
Performance: ~2.3x performance improvement
Code Size: roughly +5KB (2x)
First, some pretty straightforward changes:
finish
to not have NotWriteable and MessageTooLong in iterror.CompressionNotSupported
is renamed to error.CompressionUnsupported
, matching the naming convention from all the other errors in the same set.Next, removed the ability to heap-allocate the buffer for headers. The buffer for HTTP headers is now always provided via a static buffer. As a consequence, OutOfMemory
is no longer a member of the read()
error set, and the API and implementation of Client and Server are simplified. error.HttpHeadersExceededSizeLimit
is renamed to error.HttpHeadersOversize
.
Finally, the big changes:
Instead, some headers are provided via explicit field names populated while parsing the HTTP request/response, and some are provided via new fields that support passing extra, arbitrary headers. This resulted in simplification of logic in many places, as well as elimination of the possibility of failure in many places. There is less deinitialization code happening now. Furthermore, it made it no longer necessary to clone the headers data structure in order to handle redirects.
http_proxy and https_proxy fields are now pointers since it is common for them to be unpopulated.
loadDefaultProxies
is changed into initDefaultProxies
to communicate that it does not actually load anything from disk or from the network. The function now is leaky; the API user must pass an already instantiated arena allocator. Removes the need to deinitialize proxies.
Before, proxies stored arbitrary sets of headers. Now they only store the authorization value.
Removed the duplicated code between https_proxy and http_proxy. Finally, parsing failures of the environment variables result in errors being emitted rather than silently ignoring the proxy.
Mainly, this removes the poorly named wait
, send
, finish
functions, which all operated on the same "Response" object, which was actually being used as the request.
Now, it looks like this:
std.net.Server.accept()
gives you a std.net.Server.Connection
std.http.Server.init()
with the connectionServer.receiveHead()
gives you a RequestRequest.reader()
gives you a body readerRequest.respond()
is a one-shot, or Request.respondStreaming()
creates
a Response
Response.writer()
gives you a body writerResponse.end()
finishes the response; Response.endChunked()
allows
passing response trailers.In other words, the type system now guides the API user down the correct path.
receiveHead
allows extra bytes to be read into the read buffer, and then will reuse those bytes for the body or the next request upon connection reuse.
respond()
, the one-shot function, will send the entire response in one syscall.
Streaming response bodies no longer wastefully wraps every call to write with a chunk header and trailer; instead it only sends the HTTP chunk wrapper when flushing. This means the user can still control when it happens but it also does not add unnecessary chunks.
Empirically, the usage code is significantly less noisy, it has less error handling while handling errors more correctly, it's more obvious what is happening, and it is syscall-optimal.
Additionally:
std.http.HeadParser
from protocol.zigstd.Server.Connection
; use std.net.Server.Connection
instead.std.http.Client
has not yet been reworked in a similar manner as std.http.Server
.
In Zig 0.11.0, the deflate implementation was ported from the Go standard library, which had a bunch of undesirable properties, such as incorrect use of global variables, comments about Go's optimizer in Zig's codebase, and the requirement of dynamic memory allocation.
Zig 0.12.0 has a new implementation that is not a port of an existing codebase.
The new implementation is roughly 1.2-1.4x faster in decompression and 1.1-1.2x faster in compression. Compressed sizes are pretty much the same in both cases (source).
The new code uses static allocations for all structures, doesn't require allocator. That makes sense especially for deflate where all structures, internal buffers are allocated to the full size. Little less for inflate where the previous verision used less memory by not preallocating to theoretical max size array which are usually not fully used.
For deflate the new implementaiton allocates 395K while previous implementation used 779K. For inflate the new implementation allocates 74.5K while the old one around 36K.
Inflate difference is because we here use 64K history instead of 32K previously.
Migration guide:
For example, let's look at std.posix.termios
:
For example previously this is how you would set immediate mode on a tty:
Now the middle part looks like this:
This is thanks to the new definitions based on packed struct
.
Here's for example the definition of lflag
for Linux:
Many more std.posix
APIs were adjusted in a
similar way.
Zig 0.12.0 takes the opportunity to adjust the field names of some enums in std.builtin
to align
with our current naming conventions, which dictate that enum fields should be snake_case
. The following enums have been updated:
std.builtin.AtomicOrder
std.builtin.ContainerLayout
std.builtin.Endian
std.builtin.FloatMode
std.builtin.GlobalLinkage
std.builtin.LinkMode
Previously, when one wanted to override defaults, such as the logging function used by std.log
, they would have to define std_options
in their root file, like so:
Note how std_options
above is a struct type definiton. In this release std_options
is now an instance of std.Options
, making the process of defining overrides less error-prone.
The code above would look like this now:
And this is the definition of std.Options
to see what else you can override.
This adds std.debug.SafetyLock
and uses it in standard library hash maps by adding lockPointers()
and unlockPointers()
.
This provides a way to detect when an illegal modification has happened and panic rather than invoke undefined behavior:
Something nice about this is that if you use the "assume capacity" variants then such mutations are actually well-defined, and don't trigger the problem:
Follow-up tasks that did not make the release cutoff:
Makes the zig build system significantly more friendly to system package maintainers by introducing System Integration Options.
Let's examine this feature using groovebasin as an example project:
--- a/build.zig
+++ b/build.zig
@@ -5,18 +5,8 @@ pub fn build(b: *std.Build) void {
const optimize = b.standardOptimizeOption(.{
.preferred_optimize_mode = .ReleaseSafe,
});
- const libgroove_optimize_mode = b.option(
- std.builtin.OptimizeMode,
- "libgroove-optimize",
- "override optimization mode of libgroove and its dependencies",
- );
const use_llvm = b.option(bool, "use-llvm", "LLVM backend");
- const groove_dep = b.dependency("groove", .{
- .optimize = libgroove_optimize_mode orelse .ReleaseFast,
- .target = target,
- });
-
b.installDirectory(.{
.source_dir = .{ .path = "public" },
.install_dir = .lib,
@@ -31,7 +21,22 @@ pub fn build(b: *std.Build) void {
.use_llvm = use_llvm,
.use_lld = use_llvm,
});
- server.linkLibrary(groove_dep.artifact("groove"));
+
+ if (b.systemIntegrationOption("groove", .{})) {
+ server.linkSystemLibrary("groove");
+ } else {
+ const libgroove_optimize_mode = b.option(
+ std.builtin.OptimizeMode,
+ "libgroove-optimize",
+ "override optimization mode of libgroove and its dependencies",
+ );
+ const groove_dep = b.dependency("groove", .{
+ .optimize = libgroove_optimize_mode orelse .ReleaseFast,
+ .target = target,
+ });
+ server.linkLibrary(groove_dep.artifact("groove"));
+ }
+
b.installArtifact(server);
const run_cmd = b.addRunArtifact(server);
With this diff plus some similar changes in the project's dependency tree...
There is a new --help
section:
System Integration Options: --system [dir] System Package Mode. Disable fetching; prefer system libs -fsys=[name] Enable a system integration -fno-sys=[name] Disable a system integration Available System Integrations: Enabled: groove no z no mp3lame no vorbis no ogg no
Now, re-run the command but removing -fsys=z
:
System package maintainers can provide the new --release
option in order to
set a system-wide preference for optimization mode, while respecting the application developer's choice.
--release[=mode] Request release mode, optionally specifying a preferred optimization mode: fast, safe, small
This option may be set even if the project's build script does not explicitly expose an optimization configuration option.
--system
prevents Zig from fetching packages. Instead, a directory of packages
is provided, populated presumably by the system package manager.
--- a/build.zig
+++ b/build.zig
- const groove_dep = b.dependency("groove", .{
- .optimize = libgroove_optimize_mode orelse .ReleaseFast,
- .target = target,
- });
+ if (b.lazyDependency("groove", .{
+ .optimize = libgroove_optimize_mode orelse .ReleaseFast,
+ .target = target,
+ })) |groove_dep| {
+ server.linkLibrary(groove_dep.artifact("groove"));
+ }
--- a/build.zig.zon
+++ b/build.zig.zon
@@ -5,6 +5,7 @@
.groove = .{
.url = "https://github.com/andrewrk/libgroove/archive/66745eae734e986cd478e7220664f2de902d10a1.tar.gz",
.hash = "1220285f0f6b2be336519a0e612a11617c655f78b0efe1cac12fc73fc1e50c7b3e14",
+ .lazy = true,
},
},
.paths = .{
This makes the dependency only get fetched if it is actually used. The build runner will be rebuilt if any missing lazy dependencies are encountered.
There is an error for using dependency()
instead of lazyDependency()
:
It's allowed to do the reverse - lazyDependency()
when the manifest file does not mark it as lazy.
It's probably best practice to always use lazyDependency()
in build.zig.
This adds the *std.Build
owner to LazyPath so
that lazy paths returned from a dependency can be used in the application's
build script without friction or footguns.
Migration guide:
Source-Relative LazyPath:
↓LazyPath.relative
↓Test Runner
↓The intent for Compile.installHeader
and friends has always been to
bundle the headers alongside an artifact, have them be installed together
with the artifact and get automatically added to the include search paths
of modules that link with the artifact.
In Zig 0.11.0, however, these functions modified the default
install
top-level step of the builder, lead to a number of
unexpected results such as installing or not installing the headers
depending on which top-level build steps are invoked.
Zig 0.12.0 changes it so that installed headers are added to the compile
step itself instead of modifying the top-level install step. To handle the
construction of the include search path for dependent linking modules, an
intermediary WriteFile
step responsible for constructing the appropriate
include tree is created and set up the first time a module links to an
artifact.
Migration guide:
Compile.installHeader
now takes a LazyPath
:
Compile.installConfigHeader
has had its second argument removed and now
uses the value of include_path
as its sub path, for parity with
Module.addConfigHeader
. Use
artifact.installHeader(config_h.getOutput(), "foo.h")
if you want to set the sub path to something different.
Compile.installHeadersDirectory
/installHeadersDirectoryOptions
have been consolidated into Compile.installHeadersDirectory
, which takes a
LazyPath
and allows exclude/include filters just like InstallDir
.
Additionally:
b.addInstallHeaderFile
now takes a LazyPath
.-femit-h
header is no longer emitted even when the user specifies an
override for h_dir
. If you absolutely need the emitted header, you now
need to do install_artifact.emitted_h = artifact.getEmittedH()
until
-femit-h
is fixed.WriteFile.addCopyDirectory
, which functions very similar to InstallDir.InstallArtifact
has been updated to install the bundled headers alongide the artifact. The bundled headers are installed to the directory specified by h_dir
(which is zig-out/include
by default).Given a struct that corresponds to the build.zig of a dependency, b.dependencyFromBuildZig
returns that same dependency. In other words, if you have already @import
ed a dependency's build.zig struct, you can use this function to obtain a corresponding Dependency
:
This function is useful for packages that expose functions from their build.zig files that need to use their corresponding Dependency
, such as for accessing package-relative paths, or for running system commands and returning the output as lazy paths. This can now be accomplished through:
The x86 backend is now passing 1765/1828 (97%) of the behavior test suite, compared to the LLVM backend. It is far enough along that it is sometimes useful while developing, mainly due to the fact that it offers dramatically faster compilation speed:
Benchmark 1 (8 runs): zig-0.12.0 build-exe hello.zig measurement mean ± σ min … max outliers delta wall_time 667ms ± 26.7ms 643ms … 729ms 1 (13%) 0% peak_rss 175MB ± 19.3MB 168MB … 223MB 1 (13%) 0% cpu_cycles 3.42G ± 532M 3.21G … 4.74G 1 (13%) 0% instructions 6.20G ± 1.05G 5.83G … 8.79G 1 (13%) 0% cache_references 241M ± 19.9M 234M … 291M 1 (13%) 0% cache_misses 48.3M ± 1.26M 47.7M … 51.4M 1 (13%) 0% branch_misses 35.3M ± 4.07M 33.7M … 45.4M 1 (13%) 0% Benchmark 2 (26 runs): zig-0.12.0 build-exe hello.zig -fno-llvm -fno-lld measurement mean ± σ min … max outliers delta wall_time 196ms ± 5.77ms 187ms … 208ms 0 ( 0%) ⚡- 70.6% ± 1.7% peak_rss 88.7MB ± 721KB 87.8MB … 90.4MB 2 ( 8%) ⚡- 49.3% ± 4.3% cpu_cycles 842M ± 6.01M 836M … 866M 1 ( 4%) ⚡- 75.4% ± 6.0% instructions 1.60G ± 9.62K 1.60G … 1.60G 0 ( 0%) ⚡- 74.1% ± 6.5% cache_references 56.6M ± 378K 56.0M … 57.3M 0 ( 0%) ⚡- 76.6% ± 3.2% cache_misses 8.43M ± 104K 8.30M … 8.79M 2 ( 8%) ⚡- 82.5% ± 1.0% branch_misses 7.20M ± 30.2K 7.15M … 7.28M 2 ( 8%) ⚡- 79.6% ± 4.4%
This backend can be accessed when compiling for an x86_64 target by passing the CLI options
-fno-llvm -fno-lld
, or by setting the build system
flags use_llvm
and use_lld
on
std.Build.Step.Compile
to false
. This backend is
now able to compile many Zig projects, including the compiler itself.
Remaining tasks until it can be selected by default instead of LLVM for debug builds:
Zig now supports compiling (and cross-compiling) Windows resource scripts (.rc
files) and .manifest
files, and linking the resulting .res
files into the resource table of PE/COFF binaries.
See Zig is now also a Windows resource compiler for some use-cases of this feature and details of how to use it.
Zig now supports ELF linking for x86_64, aarch64, and partial support for riscv64.
Dependency on LLD is expected to be dropped during the next release cycle.
The -fno-lld
flag can be used to use Zig's linker where it is not currently the default.
This rather annoying bug is fixed now: error: StreamTooLong when recompiling; duplicate source files in cache manifest
The fix, which deduplicates files listed in the cache manifest, makes cache hits significantly faster. Data point: cache hit building hello world with static musl libc
Benchmark 1 (61 runs): master/zig build-exe hello.c -target native-native-musl -lc measurement mean ± σ min … max outliers delta wall_time 81.4ms ± 1.76ms 77.7ms … 87.1ms 1 ( 2%) 0% peak_rss 64.6MB ± 77.7KB 64.4MB … 64.7MB 0 ( 0%) 0% cpu_cycles 97.2M ± 1.04M 95.1M … 101M 1 ( 2%) 0% instructions 153M ± 11.1K 152M … 153M 0 ( 0%) 0% cache_references 2.21M ± 97.1K 2.05M … 2.54M 2 ( 3%) 0% cache_misses 529K ± 24.4K 486K … 600K 4 ( 7%) 0% branch_misses 409K ± 6.45K 397K … 437K 1 ( 2%) 0% Benchmark 2 (189 runs): cache-dedup/zig build-exe hello.c -target native-native-musl -lc measurement mean ± σ min … max outliers delta wall_time 25.8ms ± 1.26ms 23.9ms … 30.7ms 11 ( 6%) ⚡- 68.4% ± 0.5% peak_rss 65.2MB ± 61.8KB 65.1MB … 65.4MB 2 ( 1%) 💩+ 1.0% ± 0.0% cpu_cycles 41.2M ± 608K 40.1M … 46.3M 4 ( 2%) ⚡- 57.6% ± 0.2% instructions 64.3M ± 12.6K 64.3M … 64.4M 2 ( 1%) ⚡- 57.8% ± 0.0% cache_references 1.28M ± 34.5K 1.21M … 1.35M 0 ( 0%) ⚡- 41.9% ± 0.7% cache_misses 348K ± 18.6K 297K … 396K 0 ( 0%) ⚡- 34.2% ± 1.1% branch_misses 199K ± 1.34K 197K … 206K 6 ( 3%) ⚡- 51.2% ± 0.2%
Full list of the 502 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 had several long-standing bugs relating to accessing pointers at compile time. When attempting to access pointers in a non-trivial way, such as loading a slice of an array or reinterpreting memory, you would at times be greeted with a false positive compile error stating that the comptime dereference required a certain type to have a well-defined layout.
The merge of #19630 resolves this issue. In Zig 0.12.0,
the compiler should no longer emit incorrect compile errors when doing complex things with comptime memory.
This change also includes some fixes to the logic for comptime @bitCast
; in particular,
bitcasting aggregates containing pointers no longer incorrectly forces the operation to occur at runtime.
Zig has known bugs and even some miscompilations.
Zig is immature. Even with Zig 0.12.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 17.0.6.
Zig now generates LLVM bitcode module files directly and then passes those to LLVM. This
means that a Zig compiler built without LLVM libraries can still produce .bc
files,
which can then be passed to clang for compilation.
Although musl v1.2.5 is now available upstream, this version of Zig continues to provide v1.2.4. The next release of Zig is expected to have the updated musl.
glibc versions 2.35, 2.36, 2.37, and 2.38 are now available when cross-compiling.
Based on a suggestion from Martin Storsjö, Zig now tracks the latest master branch commit of mingw-w64.
The major theme of the 0.13.0 release cycle will be compilation speed.
Some upcoming milestones we will be working towards in the 0.13.0 release cycle:
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.
Async functions regressed with the release of 0.11.0 (the previous release to this one). Their future in the Zig language is unclear due to multiple unsolved problems:
These problems are surmountable, but it will take time. The Zig team is currently focused on other priorities.
Here are all the people who landed at least one contribution into this release:
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: