https://ziglang.org/download/0.16.0/release-notes.html 0.16.0 Release Notes Carmen the Allocgator 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. If you need donation receipts or are looking to migrate away from GitHub Sponsors, we recommend donating via Every.org. This release features 8 months of work: changes from 244 different contributors, spread among 1183 commits. Perhaps most notably, this release debuts I/O as an Interface, but don't sleep on the Language Changes or enhancements to the Compiler, Build System, Linker, Fuzzer, and Toolchain which are also included in this release. Table of Contents SS * Table of Contents * Target Support + Tier System o Tier 1 o Tier 2 o Tier 3 o Tier 4 + Support Table + OS Version Requirements + Additional Platforms * Language Changes + switch + Equality Comparisons on Packed Unions + @cImport Moving to Build System + @Type Replaced with Individual Type-Creating Builtin Functions + Allow Small Integer Types to Coerce to Floats + Forbid Runtime Vector Indexes + Vectors and Arrays No Longer Support In-Memory Coercion + Forbid Trivial Local Address Returned from Functions + Unary Float Builtins Forward Result Type + @floor, @ceil, @round, @trunc Conversion to Integers + Forbid Unused Bits in Packed Unions + Forbid Pointers in Packed Structs and Unions + Allow Explicit Backing Integers on Packed Unions + Forbid Enum and Packed Types with Implicit Backing Types in Extern Contexts + Lazy Field Analysis + Pointers to Comptime-Only Types Are No Longer Comptime-Only + Explicitly-Aligned Pointer Types Now Distinct from Naturally-Aligned Pointer Types + Simplified Dependency Loop Rules + Zero-bit Tuple Fields No Longer Implicitly comptime * Standard Library + I/O as an Interface o Future o Group o Cancelation o Batch o Sync Primitives o Entropy o Time o File System o Networking o Process o File.MemoryMap o posix and os.windows removals + heap.ArenaAllocator Becomes Thread-Safe and Lock-Free + heap.ThreadSafe Allocator Removed + Add Deflate Compression, Simplify Decompression o Zlib Comparison + Expanded target support for segfault handling/unwinding + Removal of ucontext_t and related types/functions + Debug Information Reworked + Inter-Process Progress Reporting for Windows + Windows Networking Without ws2_32.dll + Completed Migration to NtDll + "Juicy Main" + Environment Variables and Process Arguments Become Non-Global + mem: introduce cut functions; rename "index of" to "find" + Selectively Walking Directory Trees + fs.path Windows Paths + fs.path.relative Became Pure + File.Stat: Make Access Time Optional + "Preopens" + Atomic/Temporary Files + Memory Locking and Protection API Moved to process + Current Directory API Renamed + Migration to "Unmanaged" Containers + PriorityDequeue + PriorityQueue + Thread.Pool Removed + Remove builtin.subsystem + Move Target.SubSystem to zig.Subsystem and update field names + Io: delete GenericReader, AnyReader, FixedBufferStream + fs.getAppDataDir Removed + Io.Writer.Allocating Alignment Field + fs.Dir.readFileAlloc + fs.File.readToEndAlloc + std.crypto: add AES-SIV and AES-GCM-SIV + std.crypto: add Ascon-AEAD, Ascon-Hash, Ascon-CHash * Build System + Ability to Override Packages Locally + Fetch Packages Into Project-Local Directory + Unit Test Timeouts + Added --error-style Flag + Added --multiline-errors Flag + Temporary Files API * Compiler + C Translation + LLVM Backend + Reworked Byval Syntax Lowering + Reworked Type Resolution + Incremental Compilation + x86 Backend + aarch64 Backend + WebAssembly Backend + Generating Import Libraries from .def Files Without LLVM + Improved Code Generation of For Loop Safety Checks * Linker + New ELF Linker * Fuzzer + Smith + Multiprocess Fuzzing + Fuzzing Infinite Mode + Crash Dumps + Numerous bugs found and fixed with the help of an AST smith * Bug Fixes + This Release Contains Bugs * Toolchain + LLVM 21 o Loop Vectorization Disabled to Work Around Regression + musl 1.2.5 + glibc 2.43 + Linux 6.19 Headers + macOS 26.4 Headers + MinGW-w64 + FreeBSD 15.0 libc + WASI libc + zig libc + zig cc + Support dynamically-linked OpenBSD libc when cross-compiling * Roadmap * Thank You Contributors! * Thank You Sponsors! Target Support SS Zero the Ziguana Zig supports a wide range of architectures and operating systems. The Support Table and Additional Platforms sections cover the targets that Zig can build programs for, while the zig-bootstrap README covers the targets that the Zig compiler itself can be easily cross-compiled to run on. Notable changes: * aarch64-freebsd, aarch64-netbsd, loongarch64-linux, powerpc64le-linux, s390x-linux, x86_64-freebsd, x86_64-netbsd, and x86_64-openbsd are now tested natively in Zig's CI, ensuring high-quality support going forward. Thanks to OSUOSL for providing AArch64 and Power ISA hardware, and IBM for providing z /Architecture hardware. * Cross-compilation support for aarch64-maccatalyst and x86_64-maccatalyst has been added. This was 'free' in a sense, since the vendored libSystem.tbd that Zig ships already provides the symbols for these targets anyway. * Initial loongarch32-linux support has been added. Note that libc is not yet supported for this target, and LLVM still considers the ABI unstable, but programs using only syscalls via std.os.linux can be built. * Basic support has been added for the Alpha, KVX, MicroBlaze, OpenRISC, PA-RISC, and SuperH architectures. For now, these targets require using either Zig's C backend with GCC or an external LLVM/Clang fork. * Support for Oracle's Solaris and IBM's AIX and z/OS has been removed. In general, the Zig project cannot support proprietary operating systems that make it unreasonably difficult to obtain system headers and thus audit contributions. Note that this does not affect illumos; being an open source fork from OpenSolaris, it remains supported. * Stack tracing support has been significantly improved across the board; almost all major targets now provide stack traces on crashes. * Various Standard Library bugs that mainly affected weakly-ordered architectures and targets with unusual page sizes have been fixed. Among others, this is known to have significantly improved reliability on AArch64 (especially w/o LSE), LoongArch, and Power ISA. * Various Standard Library and Compiler bugs preventing Zig from working on big-endian hosts have been fixed. * Big-endian ARM targets have been fixed to emit BE8 object files when targeting ARMv6+, rather than the legacy BE32 format. Tier System SS Zig's level of support for various targets is broadly categorized into four tiers with Tier 1 being the highest. The goal is for Tier 1 targets to have zero disabled tests - this will become a requirement for post-1.0.0 Zig releases. Tier 1 SS * All non-experimental language features are known to work correctly. * The Compiler can generate machine code for these targets without relying on LLVM. Tier 2 SS * The Standard Library cross-platform abstractions account for these targets. * These targets have debug info capabilities and therefore produce stack traces on failed assertions and crashes. * Libc is available for these targets when cross-compiling. * Continuous Integration machines run the module tests for these targets on every push. Tier 3 SS * The Compiler can generate machine code for these targets via LLVM . * The Linker can produce object files, libraries, and executables for these targets. * These targets are not considered experimental by LLVM. Tier 4 SS * The Compiler can generate assembly source code for these targets via LLVM. Support Table SS In the following table, indicates full support, indicates no support, and [?][?] indicates that there is partial support, e.g. only for some sub-targets, or with some notable known issues. indicates that the status is largely unknown, typically because the target is rarely exercised. Hover over other icons for details. Target Tier Lang. Std. Code Linker Debug libc CI Feat. Lib. Gen. Info x86_64-linux 1 [?][?] --------------------------------------------------------------------- aarch64-freebsd 2 [?][?] aarch64(_be)-linux 2 [?][?] aarch64-maccatalyst 2 [?][?] [?][?] aarch64-macos 2 [?][?] aarch64(_be)-netbsd 2 [?][?] aarch64-openbsd 2 [?][?] [?][?] aarch64-windows 2 [?][?] [?][?] arm-freebsd 2 [?] [?][?] arm(eb)-linux 2 [?] arm(eb)-netbsd 2 [?] [?][?] arm-openbsd 2 [?] [?][?] hexagon-linux 2 [?] loongarch64-linux 2 [?][?] mips(el)-linux 2 [?] mips(el)-netbsd 2 [?] [?][?] mips64(el)-linux 2 [?] mips64(el)-openbsd 2 [?] [?][?] powerpc-linux 2 [?] [?][?] powerpc-netbsd 2 [?] [?][?] powerpc-openbsd 2 [?] [?][?] powerpc64(le) 2 [?] [?][?] -freebsd powerpc64(le)-linux 2 [?] [?][?] [?][?] powerpc64-openbsd 2 [?] [?][?] riscv32-linux 2 [?] riscv64-freebsd 2 [?][?] [?][?] riscv64-linux 2 [?][?] riscv64-openbsd 2 [?][?] [?][?] s390x-linux 2 [?] thumb(eb)-linux 2 [?] thumb-windows 2 [?] [?][?] wasm32-wasi 2 [?][?] [?][?] x86-linux 2 [?] x86-netbsd 2 [?] [?][?] x86-openbsd 2 [?] [?][?] x86-windows 2 [?] [?][?] x86_64-freebsd 2 [?][?] x86_64-maccatalyst 2 [?][?] [?][?] x86_64-macos 2 [?][?] [?][?] x86_64-netbsd 2 [?][?] x86_64-openbsd 2 [?][?] x86_64-windows 2 [?][?] --------------------------------------------------------------------- aarch64-haiku 3 [?][?] [?][?] [?] [?] aarch64-ios 3 [?][?] [?] [?] aarch64-serenity 3 [?][?] [?][?] [?] [?] aarch64-tvos 3 [?][?] [?] [?] aarch64-visionos 3 [?][?] [?] [?] aarch64-watchos 3 [?][?] [?] [?] arm-haiku 3 [?][?] [?] [?] [?] loongarch32-linux 3 [?][?] [?] [?] [?] mips64(el)-netbsd 3 [?] [?] [?] [?] riscv64-haiku 3 [?][?] [?][?] [?] [?] riscv64-serenity 3 [?][?] [?][?] [?] [?] wasm64-wasi 3 [?] [?][?] [?][?] [?] [?] x86-haiku 3 [?][?] [?] [?] [?] x86-illumos 3 [?][?] [?] [?] [?] x86_64-dragonfly 3 [?][?] [?] [?] x86_64-haiku 3 [?][?] [?][?] [?] [?] x86_64-illumos 3 [?][?] [?][?] [?] [?] x86_64-serenity 3 [?][?] [?][?] [?] [?] --------------------------------------------------------------------- alpha-linux 4 [?][?] [?] [?] [?] [?] alpha-netbsd 4 [?] [?] [?] [?] alpha-openbsd 4 [?] [?] [?] [?] arc(eb)-linux 4 [?][?] [?] [?] csky-linux 4 [?][?] [?] [?] hppa-linux 4 [?][?] [?] [?] [?] [?] hppa-netbsd 4 [?] [?] [?] [?] hppa-openbsd 4 [?] [?] [?] [?] hppa64-linux 4 [?] [?] [?] [?] [?] m68k-haiku 4 [?][?] [?] [?] [?] [?] m68k-linux 4 [?] [?] [?] m68k-netbsd 4 [?] [?] [?] m88k-openbsd 4 [?] [?] [?] [?] [?] microblaze(el) 4 [?][?] [?] [?] [?] [?] -linux or1k-linux 4 [?] [?] [?] sh(eb)-linux 4 [?][?] [?] [?] [?] [?] sh(eb)-netbsd 4 [?] [?] [?] [?] sh-openbsd 4 [?] [?] [?] [?] sparc-linux 4 [?][?] [?] [?] [?] sparc-netbsd 4 [?] [?] [?] [?] sparc64-haiku 4 [?][?] [?][?] [?][?] [?] [?] [?] sparc64-linux 4 [?][?] [?][?] [?] sparc64-netbsd 4 [?][?] [?][?] [?] [?] sparc64-openbsd 4 [?][?] [?][?] [?] [?] xtensa(eb)-linux 4 [?] [?] [?] [?] [?] OS Version Requirements SS The Zig standard library has minimum version requirements for some supported operating systems, which in turn affect the Zig compiler itself. Operating System Minimum Version DragonFly BSD 6.0 FreeBSD 14.0 Linux 5.10 NetBSD 10.1 OpenBSD 7.8 macOS 13.0 Windows 10 Additional Platforms SS Zig also has varying levels of support for these targets, for which the tier system does not quite apply: * aarch64-driverkit * aarch64(_be)-freestanding * aarch64-uefi * alpha-freestanding * amdgcn-amdhsa * amdgcn-amdpal * amdgcn-mesa3d * arc(eb)-freestanding * arm(eb)-freestanding * arm-3ds * arm-uefi * arm-vita * avr-freestanding * bpf(eb,el)-freestanding * csky-freestanding * hexagon-freestanding * hppa(64)-freestanding * kalimba-freestanding * kvx-freestanding * lanai-freestanding * loongarch(32,64)-freestanding * loongarch(32,64)-uefi * m68k-freestanding * microblaze(el)-freestanding * mips(64)(el)-freestanding * mipsel-psp * msp430-freestanding * nvptx(64)-cuda * nvptx(64)-nvcl * or1k-freestanding * powerpc(64)(le)-freestanding * powerpc64-ps3 * propeller-freestanding * riscv(32,64)(be)-freestanding * riscv(32,64)-uefi * s390x-freestanding * sh(eb)-freestanding * sparc(64)-freestanding * spirv(32,64)-opencl * spirv(32,64)-opengl * spirv(32,64)-vulkan * thumb(eb)-freestanding * ve-freestanding * wasm(32,64)-emscripten * wasm(32,64)-freestanding * x86(_16,_64)-freestanding * x86(_64)-uefi * x86_64-driverkit * x86_64-ps4 * x86_64-ps5 * xcore-freestanding * xtensa(eb)-freestanding Language Changes SS switch SS Carmen the Allocgator packed struct and packed union may now be used as switch prong items. They are compared solely based on their backing integer, just like in equality comparisons: const U = packed union(u2) { a: i2, b: u2, }; const u: U = .{ .a = -1 }; switch (u) { .{ .b = 3 } => {}, else => unreachable, } Other newly implemented features: * decl literals and everything else requiring a result type (e.g. @enumFromInt) may now be used as switch prong items * union tag captures are now allowed for all prongs, not just inline ones * switch prongs may contain errors which are not in the error set being switched on, if these prongs contain => comptime unreachable * switch prong captures may no longer all be discarded Bug fixes: * lots of issues with switching on one-possible-value types are now fixed * the rules around unreachable else prongs when switching on errors now apply to any switch on an error, not just to switch_block_err_union, and are applied properly based on the AST * switching on void no longer requires an else prong unconditionally * lazy values are properly resolved before any comparisons with prong items * evaluation order between all kinds of switch statements is now the same, with or without label Equality Comparisons on Packed Unions SS This used to already be possible by wrapping the packed union into a packed struct. Now it's also possible without having to do that. @cImport Moving to Build System SS In the future, C Translation will be handled via the Build System rather than the @cImport language builtin, which is now deprecated. Upgrade guide: c.zig pub const c = @cImport({ @cInclude("stdio.h"); @cInclude("math.h"); @cInclude("time.h"); @cInclude("stdlib.h"); @cInclude("epoxy/gl.h"); @cInclude("GLFW/glfw3.h"); }); const c = @import("c.zig").c; [?] c.h #include #include #include #include #include #include build.zig const translate_c = b.addTranslateC(.{ .root_source_file = b.path("src/c.h"), .target = target, .optimize = optimize, }); translate_c.linkSystemLibrary("glfw", .{}); translate_c.linkSystemLibrary("epoxy", .{}); const exe = b.addExecutable(.{ .name = "tetris", .root_module = b.createModule(.{ .root_source_file = b.path("src/main.zig"), .optimize = optimize, .target = target, .imports = &.{ .{ .name = "c", .module = translate_c.createModule(), }, }, }), }); const c = @import("c"); By doing this, the translated C code will be identical to how it was before with @cImport. Alternately, you can add the official translate-c package as an explicit dependency and gain access to more translation customization options. @Type Replaced with Individual Type-Creating Builtin Functions SS Zig 0.16.0 implements long-accepted proposal #10710 to remove the @Type builtin from the language and replace it with individual builtins like @Int and @Struct. While @Type is a simple parallel to @typeInfo, in practice, it was clunky to use for common tasks, leading users to reach for helpers like std.meta.Int. Ignoring @Vector, which already existed, @Type has been replaced with 8 new builtin functions: @EnumLiteral() type @Int(comptime signedness: std.builtin.Signedness, comptime bits: u16) type @Tuple(comptime field_types: []const type) type @Pointer( comptime size: std.builtin.Type.Pointer.Size, comptime attrs: std.builtin.Type.Pointer.Attributes, comptime Element: type, comptime sentinel: ?Element, ) type @Fn( comptime param_types: []const type, comptime param_attrs: *const [param_types.len]std.builtin.Type.Fn.Param.Attributes, comptime ReturnType: type, comptime attrs: std.builtin.Type.Fn.Attributes, ) type @Struct( comptime layout: std.builtin.Type.ContainerLayout, comptime BackingInt: ?type, comptime field_names: []const []const u8, comptime field_types: *const [field_names.len]type, comptime field_attrs: *const [field_names.len]std.builtin.Type.StructField.Attributes, ) type @Union( comptime layout: std.builtin.Type.ContainerLayout, /// Either the integer tag type, or the integer backing type, depending on `layout`. comptime ArgType: ?type, comptime field_names: []const []const u8, comptime field_types: *const [field_names.len]type, comptime field_attrs: *const [field_names.len]std.builtin.Type.UnionField.Attributes, ) type @Enum( comptime TagInt: type, comptime mode: std.builtin.Type.Enum.Mode, comptime field_names: []const []const u8, comptime field_values: *const [field_names.len]TagInt, ) type Enum Literal @EnumLiteral() returns the "enum literal" type, which is the type of uncoerced enum literals like .foo. While it is equivalent to @TypeOf (.something), the new @EnumLiteral() is preferred for consistency. @Type(.enum_literal) [?] @EnumLiteral() Integer @Int is perhaps the most useful new builtin for simple metaprogramming. The usage is equivalent to the now-deprecated std.meta.Int helper: given a signedness and bit count, it returns an integer type with those properties. This new usage results in significantly more concise and readable code. @Type(.{ .int = .{ .signedness = .unsigned, .bits = 10 } }) [?] @Int(.unsigned, 10) Tuple @Tuple is equivalent to the now-deprecated std.meta.Tuple helper. It accepts a slice of types, and returns a tuple type whose fields have those types. @Type(.{ .@"struct" = .{ .layout = .auto, .fields = &.{.{ .name = "0", .type = u32, .default_value_ptr = null, .is_comptime = false, .alignment = @alignOf(u32), }, .{ .name = "1", .type = [2]f64, .default_value_ptr = null, .is_comptime = false, .alignment = @alignOf([2]f64), }}, .decls = &.{}, .is_tuple = true, } }) [?] @Tuple(&.{ u32, [2]f64 }) To simplify the language, it is no longer possible to reify tuple types with comptime fields. Pointer @Pointer returns a pointer type, equivalent to @Type(.{ .pointer = ... }). Notably, it uses the new std.builtin.Type.Pointer.Attributes type, which uses struct field default values to make the usage more concise and more closely aligned with literal pointer type syntax. @Type(.{ .pointer = .{ .size = .one, .is_const = true, .is_volatile = false, .alignment = @alignOf(u32), .address_space = .generic, .child = u32, .is_allowzero = false, .sentinel_ptr = null, } }) [?] @Pointer(.one, .{ .@"const" = true }, u32, null) @Type(.{ .pointer = .{ .size = .many, .is_const = false, .is_volatile = false, .alignment = 1, .address_space = .generic, .child = u64, .is_allowzero = false, .sentinel_ptr = &@as(u64, 0), } }) [?] @Pointer(.many, .{ .@"align" = 1 }, u64, 0) Function @Fn returns a function type, equivalent to @Type(.{ .@"fn" = ... }). Like for pointers, new helper types have been introduced to make this builtin simpler to use. Parameters are specified with two separate arguments: the first specifies all parameter types, and the second specifies "attributes" (which currently consist only of the noalias flag). @Type(.{ .@"fn" = .{ .calling_convention = .c, .is_generic = false, .is_var_args = true, .return_type = u32, .params = &.{.{ .is_generic = false, .is_noalias = false, .type = f64, }, .{ .is_generic = false, .is_noalias = true, .type = *const anyopaque, }}, } }) [?] @Fn( &.{ f64, *const anyopaque }, &.{ .{}, .{ .@"noalias" = true } }, u32, .{ .@"callconv" = .c, .varargs = true }, ) This is one of several of the new builtins which accepts arguments in a "struct of arrays" style. An advantage of this style is that it makes it easy to specify a fixed value for all elements. For instance, to use the "default" attributes .{} for all parameters, use &@splat(.{}): @Fn(param_types, &@splat(.{}), ReturnType, .{ .@"callconv" = .c }) Struct @Struct returns a struct type, equivalent to @Type(.{ .@"struct" = ... }). Like @Fn, it uses a "struct of arrays" strategy to pass information about fields. Fields are passed as three separate arrays--field names, field types, and field attributes--where the latter includes alignment, the comptime flag, and the field's default value (if any). @Type(.{ .@"struct" = .{ .layout = .@"extern", .fields = &.{.{ .name = "foo", .type = [2]f64, .default_value_ptr = null, .is_comptime = false, .alignment = 1, }, .{ .name = "bar", .type = u32, .default_value_ptr = &@as(u32, 123), .is_comptime = true, .alignment = @alignOf(u32), }}, .decls = &.{}, .is_tuple = false, } }) [?] @Struct( .@"extern", null, &.{ "foo", "bar" }, &.{ [2]f64, u32 }, &.{ .{ .@"align" = 1 }, .{ .@"comptime" = true, .default_value_ptr = &@as(u32, 123) }, }, ) Again, &@splat(.{}) is useful for specifying "default" field attributes. In some cases, it is even useful to use @splat for the field types. For instance, to create a struct with homogeneous field types of FieldType where the field names match the names of an enum type MyEnum: const MyStruct = @Struct(.auto, null, std.meta.fieldNames(MyEnum), &@splat(FieldType), &@splat(.{})); Union @Union returns a union type, equivalent to @Type(.{ .@"union" = ... }). It is quite similar to @Struct in usage. @Type(.{ .@"union" = .{ .layout = .auto, .tag_type = MyEnum, .fields = &.{.{ .name = "foo", .type = i64, .alignment = @alignOf(i64), }, .{ .name = "bar", .type = f64, .alignment = @alignOf(f64), }}, .decls = &.{}, } }) [?] @Union( .auto, MyEnum, &.{ "foo", "bar" }, &.{ i64, f64 }, &@splat(.{}), ) Enum @Enum returns an enum type, equivalent to @Type(.{ .@"enum" = ... }). It is somewhat similar to @Struct in usage, but accepts an array of field *tag values* rather than field *types*. @Type(.{ .@"enum" = .{ .tag_type = u32, .fields = &.{.{ .name = "foo", .value = 0, }, .{ .name = "bar", .value = 1, }}, .decls = &.{}, .is_exhaustive = true, } }) [?] @Enum( u32, .exhaustive, &.{ "foo", "bar" }, &.{ 0, 1 }, ) Float There is no @Float builtin, because there are only 5 runtime floating-point types, so this functionality is trivially implemented in userland. The function std.meta.Float can be used if creating float types from a bit count is required. Array There is no @Array builtin, because this functionality is trivial to implement with normal array syntax. A general Array function would look like this: fn Array(comptime len: usize, comptime Elem: type, comptime sentinel: ?Elem) type { return if (sentinel) |s| [len:s]Elem else [len]Elem; } In practice, this generality is not usually necessary, and use sites can simply be replaced with one of [len]Elem or [len:s]Elem. Opaque There is no @Opaque builtin. Instead, write opaque {}. Optional There is no @Optional builtin. Instead, write ?T. Error Union There is no @ErrorUnion builtin. Instead, write E!T. Error Set There is no @ErrorSet builtin. To simplify the language, it is no longer possible to reify error sets. Instead, declare your error sets explicitly using error{ ... } syntax. Allow Small Integer Types to Coerce to Floats SS If all possible values of an integer type can fit in a floating point type without rounding, the integer may coerce to the float without an explicit conversion. This is determined by comparing the number of bits of precision in the integer type and the significand in the floating point type. Larger integer types will still require @floatFromInt. var foo_int: u24 = 123; var foo_float: f32 = @floatFromInt(foo_int); var bar_int: u25 = 123; var bar_float: f32 = @floatFromInt(bar_int); [?] var foo_int: u24 = 123; var foo_float: f32 = foo_int; // Safe coercion var bar_int: u25 = 123; var bar_float: f32 = @floatFromInt(bar_int); // Explicit conversion is still required This is part of a larger effort to improve ergonomics for making video games in Zig. Forbid Runtime Vector Indexes SS Upgrade guide: for (0..vector_len) |i| { _ = vector[i]; } [?] // coerce the vector to an array const vector_type = @typeInfo(@TypeOf(vector)).vector; const array: [vector_type.len]vector_type.child = vector; for (&array) |elem| { _ = elem; } This was changed as part of Reworked Byval Syntax Lowering. Vectors and Arrays No Longer Support In-Memory Coercion SS If you were using @ptrCast to convert between array memory and vector memory, use coercion instead. If you were coercing from anyerror![4]i32 to anyerror!@Vector(4, i32) or similar, you need to unwrap the error first. Forbid Trivial Local Address Returned from Functions SS One thing that Zig beginners struggle with - particularly those unfamiliar with manual memory management - is returning pointers to local variables from functions. This is challenging to address, because it is legal to return an invalid pointer: fn foo() *i32 { return undefined; } This is a perfectly valid function - the illegal operation only occurs if the returned pointer is dereferenced. Even then, it's legal to have a function that unconditionally invokes illegal behavior: fn bar() noreturn { unreachable; // equivalent to foo().* } Given this function, the expression bar() is equivalent to the expression unreachable. So how then, can we make it a compile error to return an invalid pointer from a function? Syntactic pedantry. We forbid all expressions that trivially (i.e. without type checking) lower to return undefined with the justification that the expression should instead be written canonically as return undefined. Thus the following compile error was born: fn foo() *i32 { var x: i32 = 1234; return &x; } test.zig:3:13: error: returning address of expired local variable 'x' return &x; ^ test.zig:2:9: note: declared runtime-known here var x: i32 = 1234; ^ More compile errors of this nature are planned. Unary Float Builtins Forward Result Type SS Previously Zig would not forward a result type through the following builtin functions, @sqrt @sin @cos @tan @exp @exp2 @log @log2 @log10 @floor @ceil @trunc @round This has now been changed. Where previous you couldn't write, const x: f64 = @sqrt(@floatFromInt(N)); since @sqrt would not forward the f64 result type to @floatFromInt, now you can. This is part of a larger effort to improve ergonomics for making video games in Zig. @floor, @ceil, @round, @trunc Conversion to Integers SS @floor, @ceil, @round, and @trunc now can be used to convert a floating-point value to an integer value: float-conversion.zig const std = @import("std"); const expectEqual = std.testing.expectEqual; test "round to int" { try example(12, 12.34); try example(13, 12.50); } fn example(expected: u8, value: f32) !void { const actual: u8 = @round(value); try expectEqual(expected, actual); } Shell $ zig test float-conversion.zig 1/1 float-conversion.test.round to int...OK All 1 tests passed. @intFromFloat is now redundant with @trunc and is therefore deprecated. This is part of a larger effort to improve ergonomics for making video games in Zig. Forbid Unused Bits in Packed Unions SS There was not plainly one possible way of mapping packed union representation to bits, a desirable feature of other packed types. For example, enum (u5) { ... } plainly represents 5 bits in an obvious manner and is allowed in packed contexts, but ?u8 has two reasonable ways of mapping to 9 bits and is therefore not allowed in packed contexts. This ambiguity is resolved by requiring all fields of a packed union to have the same @bitSizeOf as a backing integer type. Upgrade guide: const U = packed union { x: u8, y: u16, }; [?] const U = packed union(u16) { x: packed struct(u16) { data: u8, padding: u8 = 0, }, y: u16, }; Forbid Pointers in Packed Structs and Unions SS Fields of packed struct and packed union types are no longer permitted to be pointers, implementing proposal #24657. The primary reason for this change is that constant values containing non-byte-aligned pointers cannot be represented in the vast majority of binary formats. Additionally, there are some targets on which pointers cannot be represented merely as their address bits, but have additional metadata bits too--in this case it does not make sense to pack pointers into an integer, as packed types purport to do. If you were relying on pointers in packed types, you can instead use a usize field and convert to and from a pointer using @ptrFromInt and @intFromPtr. Allow Explicit Backing Integers on Packed Unions SS Although previous versions of Zig allowed packed struct types to specify their backing integer type with the syntax packed struct(T), this was not previously permitted for packed union types. In Zig 0.16.0, this has now been allowed. packed_union_explicit_backing_int.zig // Declaring a packed union type normally const Split16 = packed union(u16) { raw: MaybeSigned16, split: packed struct { low: u8, high: u8 }, }; // Constructing a packed union type using `@Union` const MaybeSigned16 = @Union( .@"packed", u16, // backing integer type &.{ "unsigned", "signed" }, &.{ u16, i16 }, &@splat(.{}), ); test "use packed union type with explicit backing integer" { const u: Split16 = .{ .raw = .{ .unsigned = 0xFFFE } }; try testing.expectEqual(-2, u.raw.signed); try testing.expectEqual(0xFE, u.split.low); try testing.expectEqual(0xFF, u.split.high); } const testing = @import("std").testing; Shell $ zig test packed_union_explicit_backing_int.zig 1/1 packed_union_explicit_backing_int.test.use packed union type with explicit backing integer...OK All 1 tests passed. Note that due to Forbid Enum and Packed Types with Implicit Backing Types in Extern Contexts, specifying a backing type like this is sometimes required. Forbid Enum and Packed Types with Implicit Backing Types in Extern Contexts SS enum types with inferred integer tag types, and packed struct and packed union types with inferred integer backing types, are no longer considered valid extern types. This implements proposal #24714. This breaking change was made to avoid the ABI of a type being determined entirely implicitly based solely on its fields. In particular, this matters because u8 and i8 may have differing ABIs in some contexts, and it is not clear which is being used if the choice is implicit. If this has introduced a compile error in your code, resolve it by adding an explicit tag type or backing type. (See Allow Explicit Backing Integers on Packed Unions for a related language change in Zig 0.16.0.) extern_implicit_backing_type.zig const Enum = enum { a, b, c, d }; const PackedStruct = packed struct { a: u4, b: u4 }; const PackedUnion = packed union { a: u8, b: i8 }; export var some_enum: Enum = .a; export var some_packed_struct: PackedStruct = .{ .a = 1, .b = 2 }; export var some_packed_union: PackedUnion = .{ .a = 123 }; Shell $ zig test extern_implicit_backing_type.zig /home/ci/.cache/act/b4cbebb9769d83e9/hostexecutor/src/download/0.16.0/release-notes/extern_implicit_backing_type.zig:5:1: error: unable to export type 'extern_implicit_backing_type.Enum' export var some_enum: Enum = .a; ^~~~~~ /home/ci/.cache/act/b4cbebb9769d83e9/hostexecutor/src/download/0.16.0/release-notes/extern_implicit_backing_type.zig:1:14: note: integer tag type of enum is inferred const Enum = enum { a, b, c, d }; ^~~~~~~~~~~~~~~~~~~ /home/ci/.cache/act/b4cbebb9769d83e9/hostexecutor/src/download/0.16.0/release-notes/extern_implicit_backing_type.zig:1:14: note: consider explicitly specifying the integer tag type /home/ci/.cache/act/b4cbebb9769d83e9/hostexecutor/src/download/0.16.0/release-notes/extern_implicit_backing_type.zig:1:14: note: enum declared here /home/ci/.cache/act/b4cbebb9769d83e9/hostexecutor/src/download/0.16.0/release-notes/extern_implicit_backing_type.zig:6:1: error: unable to export type 'extern_implicit_backing_type.PackedStruct' export var some_packed_struct: PackedStruct = .{ .a = 1, .b = 2 }; ^~~~~~ /home/ci/.cache/act/b4cbebb9769d83e9/hostexecutor/src/download/0.16.0/release-notes/extern_implicit_backing_type.zig:6:1: note: inferred backing integer of packed struct has unspecified signedness /home/ci/.cache/act/b4cbebb9769d83e9/hostexecutor/src/download/0.16.0/release-notes/extern_implicit_backing_type.zig:2:29: note: struct declared here const PackedStruct = packed struct { a: u4, b: u4 }; ~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~ /home/ci/.cache/act/b4cbebb9769d83e9/hostexecutor/src/download/0.16.0/release-notes/extern_implicit_backing_type.zig:7:1: error: unable to export type 'extern_implicit_backing_type.PackedUnion' export var some_packed_union: PackedUnion = .{ .a = 123 }; ^~~~~~ /home/ci/.cache/act/b4cbebb9769d83e9/hostexecutor/src/download/0.16.0/release-notes/extern_implicit_backing_type.zig:7:1: note: inferred backing integer of packed union has unspecified signedness /home/ci/.cache/act/b4cbebb9769d83e9/hostexecutor/src/download/0.16.0/release-notes/extern_implicit_backing_type.zig:3:28: note: union declared here const PackedUnion = packed union { a: u8, b: i8 }; ~~~~~~~^~~~~~~~~~~~~~~~~~~~~~ [?] extern_explicit_backing_type.zig const Enum = enum(u8) { a, b, c, d }; const PackedStruct = packed struct(u8) { a: u4, b: u4 }; const PackedUnion = packed union(u8) { a: u8, b: i8 }; export var some_enum: Enum = .a; export var some_packed_struct: PackedStruct = .{ .a = 1, .b = 2 }; export var some_packed_union: PackedUnion = .{ .a = 123 }; Shell $ zig test extern_explicit_backing_type.zig All 0 tests passed. Lazy Field Analysis SS Ziggy the Ziguana A problem we noticed since introducing I/O as an Interface is that if a type is used as a namespace, its fields will be analyzed anyway. For instance, using std.Io.Writer in any way pulls in the vtable of std.Io. Some cases of this could even result in unnecessary codegen, which can bloat binaries. Now, struct (reminder that files are structs), union, enum, and opaque are only resolved when its size or the type of one of its fields is required. This means that not only can you use types as namespaces without referencing them, but you can even use non-dereferenced pointers *T without needing T to be resolved. This was changed as part of Reworked Type Resolution. Pointers to Comptime-Only Types Are No Longer Comptime-Only SS For instance, though comptime_int is a comptime-only type, * comptime_int is not, and neither is []comptime_int. This may seem confusing at first--the easiest way to understand it is to consider function pointers. The type *const fn () void is a runtime type. However, you are not allowed to dereference it at runtime, because the element type (the function body type fn () void) is comptime-only. So these pointers can exist at runtime, but may only be dereferenced at compile-time. This makes them more-or-less useless at runtime--but there's actually an exception to that! Suppose you have a []const std.builtin.Type.StructField, and you want to pass the name of each field to runtime code somehow. Previously, you would have done this by constructing a separate []const []const u8. However, now, you can pass the []const std.builtin.Type.StructField directly to a runtime function. Naturally, this function cannot load a StructField from this slice at runtime. However, what it can do is load the name field, because it has a runtime type! This was changed as part of Reworked Type Resolution. Explicitly-Aligned Pointer Types Now Distinct from Naturally-Aligned Pointer Types SS Previously, *u8 and *align(1) u8 were considered by Zig to be literally the same type; they would compare equal, and *u8 was considered the canonical spelling (it's what the compiler would print). Now, those two types are no longer considered equivalent. Crucially, the two types can still be used interchangeably. They coerce to one another, even through pointers (what the compiler calls "in-memory coercions"), and in almost every case there is no need to care which one you have. You could think of this difference as being like the difference between u32 and c_uint: technically they are different types, but (assuming your target has 32-bit int) they act identically for all intents and purposes, and it doesn't technically matter which one you pick. This was changed as part of Reworked Type Resolution. Simplified Dependency Loop Rules SS There are new cases which are now dependency loops when they previously were not. However, it's now more obvious why a dependency loop exists due to simplified type checking rules and enhanced compile errors. This also reduces the difficulty of formally specifying the Zig language. This was changed as part of Reworked Type Resolution. Zero-bit Tuple Fields No Longer Implicitly comptime SS Back in 0.14.0, a rule was unintentionally introduced that tuple fields with zero-bit types are implicitly promoted to comptime fields: comptime { const S = struct { void }; @compileLog(@typeInfo(S).@"struct".fields[0].is_comptime); // @as(bool, true) } Zig 0.16.0 reverts this change: the above tuple field is no longer considered a comptime field. However, this does *not* prevent the field value from always being comptime-known: test "zero-bit tuple field is comptime-known" { const S = struct { u32, void }; var runtime_known: S = undefined; runtime_known = .{ 123, {} }; // Even though the tuple is runtime-known, the zero-bit field is comptime-known: comptime assert(runtime_known[1] == {}); } const assert = @import("std").debug.assert; In other words, this change is almost entirely non-breaking. The only case where it could affect old code is if you were directly relying on std.builtin.StructField.is_comptime from @typeInfo, or on the equivalence of tuples with and without explicitly declared comptime fields: //! These tests both passed in Zig 0.15.x, but fail in Zig 0.16.x. test "zero-bit tuple field is comptime" { const S = struct { void }; try expect(@typeInfo(S).@"struct".fields[0].is_comptime); } test "comptime annotation on zero-bit field is irrelevant to type equivalence" { const A = struct { void }; const B = struct { comptime void = {} }; try expect(A == B); } const expect = @import("std").testing.expect; Standard Library SS Added: * Io.Dir.renamePreserve: rename operation without replacing the destination file * Io.net.Socket.createPair Removed: * SegmentedList * meta.declList * Io.GenericWriter * Io.AnyWriter * Io.null_writer * Io.CountingReader * Thread.Mutex.Recursive Error set changes: * error.RenameAcrossMountPoints [?] error.CrossDevice * error.NotSameFileSystem [?] error.CrossDevice * error.SharingViolation [?] error.FileBusy * error.EnvironmentVariableNotFound [?] error .EnvironmentVariableMissing * std.Io.Dir.rename returns error.DirNotEmpty rather than error .PathAlreadyExists Uncategorized changes: * fmt: Formatter [?] Alt * fmt: format [?] std.Io.Writer.print * fmt: FormatOptions [?] Options * fmt: bufPrintZ [?] bufPrintSentinel * compress: lzma, lzma2, and xz updated to Io.Reader / Io.Writer * DynLib: removed Windows support. Now users must use LoadLibraryExW and GetProcAddress directly, which is probably what they were already doing anyway. * math.sign: return smallest integer type that fits possible values * Trigger automatic fetching of root certificates on Windows * tar.extract: sanitize path traversal * BitSet, EnumSet: replace initEmpty, initFull with decl literals I/O as an Interface SS Zero the Ziguana Starting with Zig 0.16.0, all input and output functionality requires being passed an Io instance. Generally, anything that potentially blocks control flow or introduces nondeterminism is grounds for being owned by the I/O interface. Along with the interface, this release comes with the following implementations: * Io.Threaded - based on threads. With this implementation, I/O operations are straightforward. For example, File System operations directly call read, write, open, close, etc. When updating code from Zig 0.15.x, using this implementation provides the equivalent behavior. This implementation is feature-complete and well-tested, including Cancelation. This is the implementation currently chosen by "Juicy Main". + -fno-single-threaded - supports task-level concurrency and cancelation. + -fsingle-threaded - does not support task-level concurrency or cancelation. * Io.Evented - work-in-progress, experimental, serving to inform the evolution of the interface. This implementation is based on userspace stack switching with work stealing, also known as M:N threading, "green threads", or stackful coroutines. + Io.Uring - although it was not the focus of this release cycle, there is already a proof-of-concept implementation based on Linux's excellent io_uring API. This backend has really nice properties but it's not finished yet. It's lacking Networking, error handling, test coverage, and minimal task stack allocations. + Io.Kqueue - proof-of-concept only, enough to fix a common bug in other async runtimes. + Io.Dispatch - based on Grand Central Dispatch (macOS). * Io.failing - simulates a system supporting no operations. Overview: * Future - task-level abstraction based on functions. Allows introducing operational independence (asynchrony) among any set of function calls. * Group - efficiently manages many independent tasks. Supports awaiting and canceling all tasks in the group together. * Queue(T) - many producer, many consumer, thread-safe, runtime configurable buffer size. When buffer is empty, consumers suspend and are resumed by producers. When buffer is full, producers suspend and are resumed by consumers. * Select - executes tasks together, providing a mechanism to wait until one or more tasks complete. Similar to Batch but operates at the higher level task abstraction layer rather than lower level Operation abstraction layer. * Batch - lower level abstraction based on introducing independence among any set of operations. * Clock, Duration, Timestamp, Timeout - type safety for units of measurement Demo of making an HTTP request to a domain: http-get.zig const std = @import("std"); const Io = std.Io; pub fn main(init: std.process.Init) !void { const gpa = init.gpa; const io = init.io; const args = try init.minimal.args.toSlice(init.arena.allocator()); const host_name: Io.net.HostName = try .init(args[1]); var http_client: std.http.Client = .{ .allocator = gpa, .io = io }; defer http_client.deinit(); var request = try http_client.request(.HEAD, .{ .scheme = "http", .host = .{ .percent_encoded = host_name.bytes }, .port = 80, .path = .{ .percent_encoded = "/" }, }, .{}); defer request.deinit(); try request.sendBodiless(); var redirect_buffer: [1024]u8 = undefined; const response = try request.receiveHead(&redirect_buffer); std.log.info("received {d} {s}", .{ response.head.status, response.head.reason }); } Shell $ zig build-exe http-get.zig $ ./http-get example.com info: received 200 OK Thanks to the fact that networking is now taking advantage of the new std.Io interface, this code has the following properties: * It asynchronously sends out DNS queries to each configured nameserver. * As each response comes in, it immediately, asynchronously tries to TCP connect to the returned IP address. * Upon the first successful TCP connection, all other in-flight connection attempts are canceled, including DNS queries. * The code also works when compiled with -fsingle-threaded even though the operations happen sequentially. * On Windows, this all happens without ws2_32.dll dependency. init: std.process.Init is thanks to "Juicy Main". When upgrading code, if you find yourself without access to an Io instance, you can get one like this: var threaded: Io.Threaded = .init_single_threaded; const io = threaded.io(); This works as long as you don't need task-level concurrency, however, it is a non-ideal workaround - like reaching for std.heap.page_allocator when you need an Allocator and do not have one. Instead, it is better to accept an Io parameter if you need one (or store one on a context struct for convenience). Point is that the application's main function should generally be responsible for constructing the Io instance used throughout. When testing, it is recommended to use std.testing.io (much like std.testing.allocator). Future SS Futures are a task-level abstraction based on functions. io.async creates a Future(T) where T is the return type of the callee. async expresses asynchrony: that the function call is independent from other logic. Creating such a task is therefore infallible and portable across limited Io implementations including those which lack a concurrency mechanism. It is legal for Io implementations to implement async calls simply by directly calling the function before returning. io.concurrent is the same as io.async except communicates that the operation must be done concurrently for correctness. This necessarily requires memory allocation because that is the nature of doing things simultaneously. This function can therefore fail with error .ConcurrencyUnavailable. In both cases, a Future(T) is created. This struct has two methods: * await - logically blocks control flow until the task completes, returning the return value of the function. * cancel - equivalent to await except also requests the Io implementation to interrupt the operation and return error .Canceled. Most I/O operations now have error.Canceled in their error sets. Use this pattern to avoid resource leaks and handle Cancelation gracefully: var foo_future = io.async(foo, .{args}); defer if (foo_future.cancel(io)) |resource| resource.deinit() else |_| {} var bar_future = io.async(bar, .{args}); defer if (bar_future.cancel(io)) |resource| resource.deinit() else |_| {} const foo_result = try foo_future.await(io); const bar_result = try bar_future.await(io); If the foo or bar function does not return a resource that must be freed, then the if can be simplified to _ = foo.cancel(io) catch {}, and if the function returns void, then the discard can also be removed. The cancel is necessary however because it releases the async task resource when errors (including error.Canceled) are returned. Group SS Groups are appropriate when many tasks share the same lifetime. They offer a O(1) overhead for spawning N tasks. group.zig const std = @import("std"); const Io = std.Io; test "sleep sort" { const io = std.testing.io; // Initialize an array with 10 random numbers. const rng_impl: std.Random.IoSource = .{ .io = io }; const rng = rng_impl.interface(); var array: [10]i32 = undefined; for (&array) |*elem| elem.* = rng.uintLessThan(u16, 1000); var sorted: [10]i32 = undefined; var index: std.atomic.Value(usize) = .init(0); // Spawn a task for each element that sleeps a number of milliseconds equal // to the element value, then adds the element. var group: Io.Group = .init; defer group.cancel(io); for (&array) |elem| group.async(io, sleepAppend, .{ io, &sorted, &index, elem }); try group.await(io); // Ensure the result is sorted. for (sorted[0 .. sorted.len - 1], sorted[1..]) |a, b| { try std.testing.expect(a <= b); } } fn sleepAppend(io: Io, result: []i32, i_ptr: *std.atomic.Value(usize), elem: i32) !void { try io.sleep(.fromMilliseconds(elem), .awake); result[i_ptr.fetchAdd(1, .monotonic)] = elem; } Shell $ zig test group.zig 1/1 group.test.sleep sort...OK All 1 tests passed. Cancelation SS Lo! Lest one learn a lone release lesson, let proclaim: "cancelation" should seriously only be spelt thusly (single "l"). Let not evil, godless liars lead afoul. In the same vein as breaking out of a for loop early, once you start doing multiple tasks concurrently, you start running into situations where one task having completed, for example by failing, means that you would like to interrupt other ongoing tasks since their results and/or side-effects are already known not to matter - or perhaps even require being reversed. Future, Group, and Batch APIs all support requesting cancelation. When cancelation is requested, the request may or may not be acknowledged. Acknowledged cancelation requests cause I/O operations to return error.Canceled. Even Io.Threaded supports cancelation by sending a signal to a thread, causing blocking syscalls to return EINTR, and responding to that error code by checking for a cancelation request before retrying the syscall. Only the logic that made the cancelation request can soundly ignore an error.Canceled. Otherwise, there are three ways to handle error .Canceled. In order of most common: 1. Propagate it. 2. After receiving it, io.recancel() and then don't propagate it. This rearms the cancelation request, so that the next check will have a chance to detect and acknowledge the request. 3. Make it unreachable with io.swapCancelProtection(). In general, cancelation is equivalent to awaiting, aside from the request to cancel. This means you can still receive the return value from the task - which may in fact have completed successfully despite the request. In this case, the side effects, such as resource allocation, should be accounted for. Here is an example of opening a file and then immediately canceling the task. Note that we must account for the possibility that the file succeeds in being opened. cancel.zig const std = @import("std"); const Io = std.Io; test "trivial cancel demo" { const io = std.testing.io; var file_task = io.async(Io.Dir.openFile, .{ .cwd(), io, "hello.txt", .{} }); defer if (file_task.cancel(io)) |file| file.close(io) else |_| {}; } Shell $ zig test cancel.zig 1/1 cancel.test.trivial cancel demo...OK All 1 tests passed. Typically, since both await and cancel are idempotent, the most useful pattern is to defer a cancelation after creating a task. This ensures the resources, including the concurrent tasks, are deallocated before returning from the function. Generally, Zig programmers don't need to explicitly add code to support cancelation, because error.Canceled is baked into the error sets of all the cancelable I/O operations. However, one can add additional cancelation points by calling io.checkCancel. It is rarely necessary to call this function. The primary use case is in long-running CPU-bound tasks which may need to respond to cancelation before completing. Batch SS You can think of Batch as a low level concurrency mechanism which provides concurrency at an Operation layer, which is efficient and portable, but more difficult to abstract around, particularly if you need to run some logic in between operations. Eventually most of the File System and Networking functionality are expected to migrate to become based on Operation, making them eligible to be used with Batch, and eligible to be used with operateTimeout, which provides a general way to add a timeout to any I/O operation. Currently the list is: * FileReadStreaming * FileWriteStreaming * DeviceIoControl * NetReceive Meanwhile Future is the equivalent but at a function abstraction layer, which is flexible and ergonomic, but it allocates task memory and error.ConcurrencyUnavailable (when using concurrent), or unwanted blocking operations (when using async), can occur in more circumstances than the lower level Batch APIs. So, generally, if you're trying to write optimal, reusable software, Batch is the way to go if you simply need to do several operations at once, otherwise, you can always use the Future APIs if that would essentially require you to reinvent futures. Or you can start with Future APIs and then optimize by reworking some stuff to use Batch later if reducing task overhead is desirable. Sync Primitives SS Sync APIs must be migrated to use the new std.Io APIs so that the code being synchronized can integrate correctly with the application's chosen I/O implementation. This will ensure, for example, when using std.Io.Threaded, a contended mutex lock will block the thread, while when using std.Io.Evented, it will switch stacks. These APIs also integrate properly with Cancelation. * std.Thread.ResetEvent [?] std.Io.Event * std.Thread.WaitGroup [?] std.Io.Group * std.Thread.Futex [?] std.Io.Futex * std.Thread.Mutex [?] std.Io.Mutex * std.Thread.Condition [?] std.Io.Condition * std.Thread.Semaphore [?] std.Io.Semaphore * std.Thread.RwLock [?] std.Io.RwLock * std.once removed; avoid global variables, or hand-roll the logic yourself Notably, lock-free sync primitives do not require std.Io integration. Entropy SS Upgrade guide: std.crypto.random.bytes var buffer: [123]u8 = undefined; std.crypto.random.bytes(&buffer); [?] var buffer: [123]u8 = undefined; io.random(&buffer); std.crypto.random (std.Random interface) const rng = std.crypto.random; [?] const rng_impl: std.Random.IoSource = .{ .io = io }; const rng = rng_impl.interface(); posix.getrandom var buffer: [64]u8 = undefined; posix.getrandom(&buffer); [?] var buffer: [64]u8 = undefined; io.random(&buffer); std.Options.crypto_always_getrandom and std.Options.crypto_fork_safety Rather than these being std wide options, they are two different std.Io APIs: /// Obtains entropy. /// /// The implementation *may* store RNG state in process memory and use it to /// fill `buffer`. /// /// The degree to which the entropy is cryptographically secure is determined /// by the `Io` implementation. /// /// Threadsafe. /// /// See also `randomSecure`. pub fn random(io: Io, buffer: []u8) void { return io.vtable.random(io.userdata, buffer); } pub const RandomSecureError = error{EntropyUnavailable} || Cancelable; /// Obtains cryptographically secure entropy from outside the process. /// /// Always makes a syscall, or otherwise avoids dependency on process memory, /// in order to obtain fresh randomness. Does not rely on stored RNG state. /// /// Does not have any fallback mechanisms; returns `error.EntropyUnavailable` /// if any problems occur. /// /// Threadsafe. /// /// See also `random`. pub fn randomSecure(io: Io, buffer: []u8) RandomSecureError!void { return io.vtable.randomSecure(io.userdata, buffer); } So if you want to keep CSPRNG state out of your process memory, call Io.randomSecure rather than Io.random. Time SS This release adds the ability to get clock resolution, which may fail. This allows error.Unexpected and error.ClockUnsupported to be removed from timeout and clock reading error sets because they can be treated as having a resolution of infinite, which is detectable by the user by separately (beforehand) calling Clock.resolution. Upgrade guide: * std.time.Instant [?] std.Io.Timestamp * std.time.Timer [?] std.Io.Timestamp * std.time.timestamp [?] std.Io.Timestamp.now File System SS All fs APIs are migrated to Io. Although it's a lot of breaking changes, unlike "writergate", this changeset is expected to be generally easy for Zig programmers to manage, because it does not require much critical thinking. For example, typical upgrade path will look something like this: file.close(); [?] file.close(io); Although your upgrade diff might be large, it will be quite simple to understand what needs to be done. Added: * Io.Dir.hardLink * Io.Dir.Reader * Io.Dir.setFilePermissions * Io.Dir.setFileOwner * Io.File.NLink Removed with no replacement: * fs.realpathZ * fs.realpathW * fs.realpathW2 * fs.makeDirAbsoluteZ * fs.deleteDirAbsoluteZ * fs.openDirAbsoluteZ * fs.renameAbsoluteZ * fs.renameZ * fs.deleteTreeAbsolute * fs.symLinkAbsoluteW * fs.Dir.realpathZ * fs.Dir.realpathW * fs.Dir.realpathW2 * fs.Dir.deleteFileZ * fs.Dir.deleteFileW * fs.Dir.deleteDirZ * fs.Dir.deleteDirW * fs.Dir.renameZ * fs.Dir.renameW * fs.Dir.symLinkWasi * fs.Dir.symLinkZ * fs.Dir.symLinkW * fs.Dir.readLinkWasi * fs.Dir.readLinkZ * fs.Dir.readLinkW * fs.Dir.adaptToNewApi * fs.Dir.adaptFromNewApi * fs.File.isCygwinPty * fs.File.adaptToNewApi * fs.File.adaptFromNewApi Changed: * fs.copyFileAbsolute [?] std.Io.Dir.copyFileAbsolute * fs.makeDirAbsolute [?] std.Io.Dir.createDirAbsolute * fs.deleteDirAbsolute [?] std.Io.Dir.deleteDirAbsolute * fs.openDirAbsolute [?] std.Io.Dir.openDirAbsolute * fs.openFileAbsolute [?] std.Io.Dir.openFileAbsolute * fs.accessAbsolute [?] std.Io.Dir.accessAbsolute * fs.createFileAbsolute [?] std.Io.Dir.createFileAbsolute * fs.deleteFileAbsolute [?] std.Io.Dir.deleteFileAbsolute * fs.renameAbsolute [?] std.Io.Dir.renameAbsolute * fs.readLinkAbsolute [?] std.Io.Dir.readLinkAbsolute * fs.symLinkAbsolute [?] std.Io.Dir.symLinkAbsolute * fs.has_executable_bit [?] std.Io.File.Permissions.has_executable_bit * fs.realpath [?] std.Io.Dir.realPathFileAbsolute * fs.rename [?] std.Io.Dir.rename * fs.cwd [?] std.Io.Dir.cwd * fs.defaultWasiCwd [?] std.os.defaultWasiCwd * fs.realpathAlloc [?] std.Io.Dir.realPathFileAbsoluteAlloc * fs.openSelfExe [?] std.process.openExecutable * fs.selfExePathAlloc [?] std.process.executablePathAlloc * fs.selfExePath [?] std.process.executablePath * fs.selfExeDirPath [?] std.process.executableDirPath * fs.selfExeDirPathAlloc [?] std.process.executableDirPathAlloc * fs.Dir.setAsCwd [?] std.process.setCurrentDir * fs.Dir.realpath [?] std.Io.Dir.realPathFile * fs.Dir.realpathAlloc [?] std.Io.Dir.realPathFileAlloc * fs.Dir [?] std.Io.Dir * fs.File [?] std.Io.File * fs.Dir.makeDir [?] std.Io.Dir.createDir * fs.Dir.makePath [?] std.Io.Dir.createDirPath * fs.Dir.makeOpenDir [?] std.Io.Dir.createDirPathOpen * fs.Dir.rename: now accepts two Dirparameters (plus Io) * fs.Dir.atomicSymLink [?] std.Io.Dir.symLinkAtomic * fs.Dir.chmod [?] std.Io.Dir.setPermissions * fs.Dir.chown [?] std.Io.Dir.setOwner * fs.File.Mode [?] std.Io.File.Permissions * fs.File.PermissionsWindows [?] std.Io.File.Permissions * fs.File.PermissionsUnix [?] std.Io.File.Permissions * fs.File.default_mode [?] std.Io.File.Permissions.default_file * fs.File.getOrEnableAnsiEscapeSupport [?] std.Io.File.enableAnsiEscapeCodes * fs.File.setEndPos [?] std.Io.File.setLength * fs.File.getEndPos [?] std.Io.File.length * fs.File.seekTo, std.fs.File.seekBy, std.fs.File.seekFromEnd [?] std.Io.File.Reader.seekTo, std.Io.File.Reader.seekBy, std.Io.File.Writer.seekTo * fs.File.getPos [?] std.Io.File.Reader.logicalPos, std.Io.Writer.logicalPos * fs.File.mode [?] std.Io.File.stat().permissions.toMode * fs.File.chmod [?] std.Io.File.setPermissions * fs.File.chown [?] std.Io.File.setOwner * fs.File.updateTimes [?] std.Io.File.setTimestamps, std.Io.File.setTimestampsNow * fs.File.read [?] std.Io.File.readStreaming * fs.File.readv [?] std.Io.File.readStreaming * fs.File.pread [?] std.Io.File.readPositional * fs.File.preadv [?] std.Io.File.readPositional * fs.File.preadAll [?] std.Io.File.readPositionalAll * fs.File.write [?] std.Io.File.writeStreaming * fs.File.writev [?] std.Io.File.writeStreaming * fs.File.pwrite [?] std.Io.File.writePositional * fs.File.pwritev [?] std.Io.File.writePositional * fs.File.writeAll [?] std.Io.File.writeStreamingAll * fs.File.pwriteAll [?] std.Io.File.writePositionalAll * fs.File.copyRange, std.fs.File.copyRangeAll [?] std.Io.File.writer Many functions now have an Io parameter. Deprecated: * fs.path [?] std.Io.Dir.path * fs.max_path_bytes [?] std.Io.Dir.max_path_bytes * fs.max_name_bytes [?] std.Io.Dir.max_name_bytes Networking SS All net APIs are migrated to Io. Io.Evented does not yet implement networking. Io.net currently lacks a way to do non-IP networking. Process SS Spawning a child process: var child = std.process.Child.init(argv, gpa); child.stdin_behavior = .Pipe; child.stdout_behavior = .Pipe; child.stderr_behavior = .Pipe; try child.spawn(io); [?] var child = try std.process.spawn(io, .{ .argv = argv, .stdin = .pipe, .stdout = .pipe, .stderr = .pipe, }); Running a child process and capturing its output: const result = std.process.Child.run(allocator, io, .{ [?] const result = std.process.run(allocator, io, .{ Replacing current process image: const err = std.process.execv(arena, argv); [?] const err = std.process.replace(io, .{ .argv = argv }); File.MemoryMap SS The pointer contents are defined to only be synchronized after explicit sync points, making it legal to have a fallback implementation based on file operations while still supporting a handful of use cases for memory mapping. Furthermore, it makes it legal for evented I/O implementations to use evented file I/O for the sync points rather than memory mapping. Technically this is a breaking change because the positional file reading and writing error sets are more constrained. Also on WASI, you now get error.IsDir correctly instead of error.NotOpenForReading. posix and os.windows removals SS Most std.posix and std.os.windows functions existed at an awkward medium-level abstraction and have thus been removed. Therefore, if you were using any functions removed from those namespaces, you must now choose a direction: * Go higher: use std.Io * Go lower: use std.posix.system directly More removals are planned. heap.ArenaAllocator Becomes Thread-Safe and Lock-Free SS Lock-free and thread-safe plays better with std.Io integration and libc integration. By avoiding locks, we avoid needing Sync Primitives and thereby avoid needing an Io instance, and also allow the Allocator to be used as the backing allocator for an Io instance. The new implementation offers comparable performance to the previous one when only being accessed by a single thread and a slight speedup compared to the previous implementation wrapped into a ThreadSafeAllocator up to ~7 threads performing operations on it concurrently. more details same thing is planned for heap.DebugAllocator heap.ThreadSafe Allocator Removed SS The only reasonable way to implement ThreadSafeAllocator, which wraps an underlying Allocator, is with a mutex, which necessarily requires an Io instance and is generally inefficient. Meanwhile, essentially every Allocator in which thread safety is desired, can be adjusted to be lock free and avoid slow, blocking mutexes altogether - or at least in some of the hot paths! ThreadSafeAllocator is an anti-pattern. This is a situation when tighter coupling is called for. Add Deflate Compression, Simplify Decompression SS Adds deflate compression, implemented from scratch. A history window is kept in the writer's buffer for matching and a chained hash table is used to find matches. Tokens are accumulated until a threshold is reached and then outputted as a block. Additionally, two other deflate writers are provided: * Raw writes only in store blocks (the uncompressed bytes). It utilizes data vectors to efficiently send block headers and data. * Huffman only performs Huffman compression on data and no matching. The above are also able to take advantage of writer semantics since they do not need to keep a history. Literal and distance code parameters in token have also been reworked. Their parameters are now derived mathematically, however the more expensive ones are still obtained through a lookup table (except on ReleaseSmall). Decompression bit reading has been greatly simplified, taking advantage of the ability to peek on the underlying reader. Additionally, a few bugs with limit handling have been fixed. Zlib Comparison SS zlib achieves a 1.00% better compression ratio at the default compression level and 0.77% better at the best compression level. It seems that zlib selects slightly different matches, however the total matched bytes is less. In the future, it would be nice to figure this out and be on par with zlib. Here is a benchmark of the performance versus zlib using the equivalent parameters (i.e. levels). With default compression level: Benchmark 1 (20 runs): sh -c ./zpipe