Zig Programming Language - Devlog 2026
27 augustus 2026: Pointerstabiliteit voor ArrayLists
Auteur: Robbie Lyman
Pointer Stability Locks zijn in 2024 toegevoegd aan de Hash Map-containers van std. Een pull-request, oorspronkelijk geopend door Leo Emar-Kar in 2025, brengt deze techniek voor het waarborgen van geheugenveiligheid nu naar std.ArrayList.
Om dit in je code te gebruiken, voeg je een aanroep toe aan lockPointers() wanneer je voor het eerst een pointer opslaat naar een element of een slice van elementen die worden ondersteund door de ArrayList, en roep je unlockPointers() aan wanneer die pointers niet langer nodig zijn.
Hier is een enigszins kunstmatig voorbeeld. Stel dat we twee ArrayLists beheren; één die de inhoud van een input in het geheugen houdt, en een andere die interessante fragmenten (bijvoorbeeld elke regel) opslaat. Hier is een versie van dit proces die een bug bevat:
const std = @import("std");
const Context = struct {
history: std.ArrayList(u8),
lines: std.ArrayList([]const u8),
fn parse(ctx: *Context, allocator: std.mem.Allocator, input: []const u8) !void {
const slice = try ctx.history.addManyAsSlice(allocator, input.len);
@memcpy(slice, input);
var it = std.mem.tokenizeScalar(u8, slice, '\n');
while (it.next()) |line| {
try ctx.lines.append(allocator, line);
}
}
};
Wat is de bug? Het probleem is dat elementen van Context.lines.items afhankelijk zijn van de locatie van Context.history.items. Deze locatie kan echter veranderen als Context.history moet groeien voorbij zijn huidige capaciteit. Hier is een reproductie van de bug:
test "Context.parse" {
const input = "I'm first!\n";
const input_two =
\\But this text
\\is juuuuuuuuuuuuuuuuuuuuuuuuust long enough that it
\\causes a problem!
\\And the problem could be that we segfault!
\\Which is no fun to run into.
;
var ctx: Context = .{
.history = .empty,
.lines = .empty,
};
const gpa = std.testing.allocator;
defer ctx.history.deinit(gpa);
defer ctx.lines.deinit(gpa);
try ctx.parse(gpa, input);
try ctx.parse(gpa, input_two);
try std.testing.expectEqualStrings("I'm first!", ctx.lines.items[0]);
}
Als deze code met zig test wordt uitgevoerd, is de output als volgt:
====== expected this output: =========
I'm first!<0xE2><0x90><0x83>
======== instead found this: =========
UUUUUUUUUU<0xE2><0x90><0x83>
======================================
First difference occurs on line 1:
expected:
I'm first!
^ ('\x49')
found:
UUUUUUUUUU
^ ('\x55')
1/1 blah.test.Context.parse...FAIL (TestExpectedEqual)
Dit bevestigt dat er een bug is, maar afhankelijk van je ervaring met het debuggen van geheugenproblemen (en je keuze van allocator), zou je lang kunnen zoeken naar de oplossing.
Wat gebeurt er als we de volgende wijziging aanbrengen, aangezien we pointers hebben opgeslagen na de eerste aanroep van parse in onze test?
try ctx.parse(gpa, input);
+ ctx.history.lockPointers();
+ defer ctx.history.unlockPointers();
try ctx.parse(gpa, input_two);
try std.testing.expectEqualStrings("I'm first!", ctx.lines.items[0]);
Nu krijgen we een panic met een stack trace die precies laat zien waar onze aanname over pointerstabiliteit is geschonden:
thread 3023222 panic: reached unreachable code
/Users/robbie/bin/lib/std/debug.zig:442:14: 0x102d2506f in assert (test)
if (!ok) unreachable; // assertion failure
^
/Users/robbie/bin/lib/std/debug.zig:1880:15: 0x102d31ef7 in assertUnlocked (test)
assert(l.state == .unlocked);
^
/Users/robbie/bin/lib/std/array_list.zig:1348:50: 0x102e3ced7 in ensureTotalCapacityPrecise (test)
self.pointer_stability.assertUnlocked();
^
/Users/robbie/bin/lib/std/array_list.zig:1341:51: 0x102e3cdff in ensureTotalCapacity (test)
return self.ensureTotalCapacityPrecise(gpa, growCapacity(new_capacity));
^
/Users/robbie/bin/lib/std/array_list.zig:1237:41: 0x102e4e5c3 in resize (test)
try self.ensureTotalCapacity(gpa, new_len);
^
/Users/robbie/bin/lib/std/array_list.zig:1461:28: 0x102e4e40f in addManyAsSlice (test)
try self.resize(gpa, try addOrOom(self.items.len, n));
^
/Users/robbie/src/advent-of-code/2024/blah.zig:8:51: 0x102e4dc1f in parse (test)
const ptr = try ctx.history.addManyAsSlice(allocator, input.len);
^
/Users/robbie/src/advent-of-code/2024/blah.zig:35:18: 0x102e4e167 in test.Context.parse (test)
try ctx.parse(gpa, input_two);
Dit is zeer behulpzaam: nu kan ik zien dat geheugenveiligheid een waarschijnlijke oorzaak is van het falen van de test, in plaats van een logische fout.
Ten slotte een subtiel punt: in tegenstelling tot HashMap is ArrayList geordend. Dit betekent dat operaties op de lijst elementen kunnen verplaatsen, zelfs zonder het geheugen van de lijst als geheel te verplaatsen, te vergroten of vrij te geven. Bijvoorbeeld: de slice die wordt geretourneerd door addManyAsSlice(gpa, n) wijst mogelijk niet meer naar de laatste $n$ elementen van de lijst als je orderedRemove() of pop() aanroept. Om deze reden zullen orderedRemove() en pop(), hoewel ze nooit alloceren, dezelfde assertion triggeren na een aanroep van lockPointers().
---
30 juni 2026: Alle functionaliteit voor pakketbeheer verplaatst van compiler naar build-systeem
Auteur: Andrew Kelley
Nu er een apart proces is voor de build.zig-scripts van gebruikers en het build-systeem zelf, is het logisch dat de logica voor pakketbeheer daar komt te staan.
De volgende subcommando's zijn verplaatst naar het maker-proces:
zig buildzig fetchzig initzig libc
Dit betekent dat grote delen die voorheen in het compiler-executable zaten, nu in bronvorm worden geleverd, waaronder:
- Logica voor het ophalen van pakketten (package fetching)
- HTTP-client en netwerken
- TLS (Transport Layer Security) en bijbehorende cryptografie
- Git-protocol
- xz, gzip, zstd, flate, zip
- Parsen, validatie en verwerking van
build.zig.zon-bestanden
Hierdoor kan deze functionaliteit nu worden gepatcht zonder de compiler opnieuw te compileren. Bovendien heeft pakketbeheer in Zig nu veiligheidschecks ingeschakeld tijdens netwerkactiviteiten, omdat het maker-executable is gecompileerd in ReleaseSafe-modus. Daarnaast kan alle cryptografie voor netwerken en bestandshashing nu gebruikmaken van speciale CPU-instructies van de host.
Wijziging in de processtructuur
Oorspronkelijk zag de procesboom er zo uit: zig build (compiler + pakketbeheer) → builder (gebruikers-logica + build-systeem)
Na de eerste scheiding werd dit: zig build (compiler + pakketbeheer) → configurer (gebruikers-logica) EN maker (build-systeem)
Na de huidige wijzigingen ziet het er zo uit: zig build (compiler) → maker (build-systeem + pakketbeheer) → configurer (gebruikers-logica)
Hierdoor kan het maker-proces blijven bestaan wanneer de configuratie opnieuw moet worden uitgevoerd, omdat het nu het ouderproces is in plaats van een sibling.
Observaties en impact Deze wijziging is bijna volledig non-breaking, maar er zijn enkele verschillen:
- Binary-grootte: De Zig-executable krimpt met 4% (van 14,1 naar 13,5 MiB, zonder LLVM, ReleaseSmall).
- Flags: De flag
--maker-optis vervangen door de omgevingsvariabeleZIGDEBUGMAKER. - Flags: De flag
--zig-lib-diris vervangen door de omgevingsvariabeleZIGLIBDIR.
De belangrijkste blokkades voor Zig 0.17.0 zijn nu:
- MVP voor het build server protocol (nodig voor ZLS).
- Introductie van pad-afhankelijkheden voor het build-script zelf.
- Zorgen dat
zig build --watchwijzigingen in het build-script detecteert en zichzelf opnieuw uitvoert. - Problemen met cache-misses bij verschillende
cwd(current working directory).
---
26 juni 2026: Vooruitgang SPIR-V Backend
Auteur: Ali Cheraghi
De SPIR-V backend was op verschillende punten verouderd na recente compiler-wijzigingen. De afgelopen weken is dit hersteld.
@SpirvType SPIR-V heeft een aantal typen die niet konden worden uitgedrukt in het type-systeem van Zig. De nieuwe @SpirvType builtin is geïntroduceerd om dit op te lossen.
const Sampler = @SpirvType(.sampler);
const Image = @SpirvType(.{ .image = .{
.usage = .{ .sampled = u32 },
.format = .unknown,
.dim = .@"2d",
.depth = .unknown,
.arrayed = false,
.multisampled = false,
.access = .unknown,
} });
const SampledImage = @SpirvType(.{ .sampled_image = Image });
const RuntimeArray = @SpirvType(.{ .runtime_array = u32 });
const sampled_image = @extern(*addrspace(.constant) const SampledImage, .{
.name = "sampled_image",
.decoration = .{ .descriptor = .{ .set = 0, .binding = 1 } },
});
Execution Mode in de Calling Convention Informatie over de execution mode (workgroup size, fragment origin, etc.) wordt nu overgebracht door de calling convention in plaats van via inline assembly OpExecutionMode. De oude std.gpu.executionMode() helper is verwijderd. Er zijn twee nieuwe calling conventions toegevoegd voor mesh shading pipelines: spirvtask en spirvmesh.
export fn vert() callconv(.spirv_vertex) void {}
export fn frag() callconv(.{ .spirv_fragment = .{ .depth_assumption = .greater } }) void {}
export fn comp() callconv(.{ .spirv_kernel = .{ .x = 8, .y = 8, .z = 1 } }) void {}
export fn task() callconv(.{ .spirv_task = .{ .x = 1, .y = 1, .z = 1 } }) void {}
export fn mesh() callconv(.{ .spirv_mesh = .{ .stage_output = .output_lines, .max_primitives = 1, .max_vertices = 2 } }) void {}
Overige verbeteringen
- Capabilities en Extensions: Deze worden nu volledig aangestuurd door de CPU-feature set, vergelijkbaar met andere targets.
- Multi-threaded Codegen: Codegen voor SPIR-V draait nu op de thread pool van de compiler in plaats van single-threaded in de linker thread. Dit bracht ook twee ISel-passes terug:
deduptypesenpruneunused. - Object File Linking:
.spv-bestanden worden nu herkend als object-bestanden, waardoor meerdere.zig-bestanden kunnen worden gekoppeld tot één module.
De std.gpu is hernoemd naar std.spirv. Hoewel er nog veel te doen is, is de backend nu aanzienlijk bruikbaarder.
---
25 juni 2026: Nieuwe @bitCast Semantiek en LLVM Backend Verbeteringen
Auteur: Matthew Lugg
LLVM Backend Integer Lowering
Zig heeft willekeurige bit-breedte integer typen (bijv. u4, i13, u40) altijd direct naar LLVM IR's bit-int typen gelowerd. Dit is niet optimaal omdat de semantiek van LLVM voor deze typen in het geheugen restrictief is voor de optimizer.
Het doel van de nieuwe PR is om deze bit-int typen alleen te gebruiken bij het manipuleren van waarden in SSA-vorm, en ze uit te breiden naar ABI-grootte typen (i8, i16, i32, etc.) wanneer ze in het geheugen worden opgeslagen.
Herdefinitie van @bitCast
De wijziging in hoe integers in het geheugen worden opgeslagen, beïnvloedde de implementatie van @bitCast. In plaats van de oude semantiek (die in feite syntax-suiker was voor het herinterpreteren van bytes in het geheugen), is er gekozen voor een nieuwe definitie.
De nieuwe semantiek @bitCast is nu gedefinieerd op basis van de bits die een type logisch representeren, in plaats van de fysieke bytes in het geheugen. Elk type dat @bitCast ondersteunt, heeft een "logische bit-layout".
- Eenvoudige typen: Een
u8naari8conversie blijft hetzelfde: de bits blijven ongewijzigd en de meest significante bit wordt als tekenbit geïnterpreteerd. - Aggregate typen (Arrays en Vectoren): Hier wijken de semantieken af. Bij het casten van
[2]u8naaru16was het resultaat voorheen afhankelijk van de endianness van het target. Onder de nieuwe semantiek is de operatie endian-agnostisch: het eerste array-element wordt altijd de 8 minst significante bits.
Dit maakt ook exotische operaties mogelijk, zoals het converteren van [2]u3 naar @Vector(3, u2):
test "bitcast [2]u3 to @Vector(3, u2)" {
const arr: [2]u3 = .{ 0b001, 0b011 };
const vec: @Vector(3, u2) = @bitCast(arr);
// Logische bitvolgorde:
// arr[0] (0b001) -> bits: 1, 0, 0
// arr[1] (0b011) -> bits: 1, 1, 0
// Totaal: 1 0 0 1 1 0
// Vector chunks (2-bit): 0b01, 0b10, 0b01
try expect(vec[0] == 0b01);
try expect(vec[1] == 0b10);
try expect(vec[2] == 0b01);
}
const expect = @import("std").testing.expect;
Overige wijzigingen en prestaties
@bitCastnaar/van vectoren van pointers is nu verboden.@bitCastop enums is nu toegestaan.- De verbeterde integer lowering in de LLVM backend leidde tot een prestatiewinst van ongeveer 5% voor de Zig-compiler zelf.
---
30 mei 2026: ELF Linker Verbeteringen
Auteur: Matthew Lugg
De nieuwe ELF linker, geïntroduceerd in Zig 0.16.0, is sterk verbeterd. Hoewel deze nog steeds standaard is uitgeschakeld (te activeren met -fnew-linker), is er veel progressie geboekt.
Een belangrijke mijlpaal is dat de nieuwe ELF linker nu in staat is om de self-hosted Zig compiler te bouwen met LLVM en LLD libraries ingeschakeld.
Snelle incrementele compilatie De belangrijkste feature is de ondersteuning voor snelle incrementele compilatie. Op x86_64 Linux is het nu mogelijk om incrementele rebuilds uit te voeren terwijl externe libraries en C-bronbestanden worden gelinkt, zonder extra prestatie-overhead. Dit betekent dat rebuilds in sommige gevallen in milliseconden kunnen gebeuren.
De grootste ontbrekende functie is momenteel de ondersteuning voor het genereren van DWARF-debuginformatie voor Zig-code.
---
26 mei 2026: Build-systeem Herzien
Auteur: Andrew Kelley
Er is een grote wijziging doorgevoerd waarbij het maker-proces is gescheiden van het configurer-proces.
Hoe het werkt Voorheen werden build.zig-bestanden en de implementatie van het build-systeem samen gecompileerd in één proces. Nu is dit opgesplitst:
- Configurer:
build.zig-bestanden worden gecompileerd in een klein proces in debug-modus. Dit proces construeert een build-graph en serialiseert deze naar een binair configuratiebestand. - Maker: De parent
zig buildproces compileert asynchroon het proces dat de build-graph uitvoert (demaker) in release-modus. Demakervoert vervolgens de build-graph uit aan de hand van het configuratiebestand.
Motivatie en voordelen
- Snelheid: Alleen de
build.zig-logica van de gebruiker hoeft bij wijzigingen te worden gecompileerd. - Caching: Het build-systeem kan het opnieuw uitvoeren van de
build.zig-logica volledig overslaan als er niets is gewijzigd. - Optimalisatie: Het proces dat de build-graph daadwerkelijk uitvoert, is nu gecompileerd met optimalisaties.
Benchmarks (zig build -h)
- Oud: 150ms (gemiddeld)
- Nieuw: 14,3ms (gemiddeld) → een verbetering van ~90%.
Breaking Change Voor de meeste gebruikers is dit de belangrijkste wijziging: if (b.args) |args| { runcmd.addArgs(args); } → runcmd.addPassthruArgs(); Build-scripts kunnen argumenten niet langer direct observeren, maar in ruil daarvoor hoeven ze niet opnieuw te worden gebouwd wanneer deze argumenten wijzigen.
---
8 april 2026: Incrementele compilatie met LLVM
Auteur: Matthew Lugg
Incrementele compilatie is nu werkend geïmplementeerd voor de LLVM backend. Hoewel dit de tijd die LLVM nodig heeft om objectbestanden te genereren ("LLVM Emit Object") niet versnelt, minimaliseert het de tijd die wordt besteed in de Zig-compiler code zelf.
Dit betekent dat compilatiefouten bij het gebruik van LLVM nu veel sneller worden gerapporteerd. Deze ondersteuning is beschikbaar in de master branch en zal in de 0.16.0 release zitten. Gebruikers kunnen dit testen met -fincremental --watch bij zig build.
---
10 maart 2026: Herontwerp van Type Resolutie
Auteur: Matthew Lugg
Er is een omvangrijke PR (30.000 regels) samengevoegd die de interne type resolutie logica van de compiler herstructureert.
Belangrijkste wijzigingen
- Lazy Analyse: De compiler is nu "luier" bij het analyseren van velden. Als een type nooit wordt geïnitialiseerd, hoeft Zig niet te weten hoe dat type eruitziet. Dit is nuttig voor typen die als namespace dienen.
- Voorbeeld: Een struct met een
@compileErrorin een veld zal nu gewoon compileren, zolang het type alleen als namespace wordt gebruikt en het veld niet wordt aangeroepen. - Verbeterde Dependency Loops: Foutmeldingen bij circulaire afhankelijkheden zijn nu gedetailleerd en tonen precies waar de loop ontstaat.
- Incrementele Compilatie: Er zijn grote verbeteringen doorgevoerd in de snelheid van incrementele compilatie door "over-analyse" te elimineren.
---
13 februari 2026: io_uring en Grand Central Dispatch std.Io implementaties
Auteur: Andrew Kelley
De std.Io.Evented implementaties voor io_uring (Linux) en Grand Central Dispatch (macOS) zijn toegevoegd. Beide zijn gebaseerd op userspace stack switching (fibers/green threads).
Deze implementaties worden beschouwd als experimenteel. Er is nog werk nodig op het gebied van foutafhandeling, het verwijderen van logging en het diagnosticeren van prestatievermindering bij gebruik in de compiler.
Het doel is dat Zig-code I/O-implementaties moeiteloos kan wisselen. De app-functie in de code blijft identiek, ongeacht of er een Threaded of Evented I/O-implementatie wordt gebruikt.
---
6 februari 2026: Twee verbeteringen aan de workflow voor pakketbeheer
Auteur: Andrew Kelley
1. Lokale opslag van pakketten Ophaalde pakketten worden nu lokaal opgeslagen in de zig-pkg directory in de projectroot (naast build.zig).
- Voordeel: Het is makkelijker om pakketten te inspecteren, te bewerken of IDE-autocompletion te configureren.
- Archivering: Hierdoor kunnen zelfstandige bron-tarballs worden gedistribueerd die alle afhankelijkheden bevatten voor offline builds.
- Global Cache: Er wordt nog steeds een globale kopie bewaard in
~/.cache/zig/p/, maar deze is nu gecomprimeerd om opslagruimte te besparen en delen via P2P (toekomstig plan) te vergemakkelijken.
2. De --fork flag Er is een nieuwe flag toegevoegd aan zig build: --fork=[pad]. Hiermee kan een project overal in de dependency tree worden overschreven door een lokale kopie van de broncode. Dit is ideaal voor het tijdelijk gebruiken van forks om ecosystem-breakage op te lossen zonder direct de build.zig.zon te hoeven aanpassen.
---
3 februari 2026: Kernel32.dll omzeilen voor plezier en non-profit
Auteur: Andrew Kelley
In Windows zijn veel API's in kernel32.dll wrappers rondom de lagere ntdll.dll. De Zig-standaardbibliotheek hanteert het beleid om de Native API te verkiezen boven Win32, omdat de wrappers vaak onnodige heap-allocaties en bloat introduceren.
Voorbeelden:
- Entropy: In plaats van
advapi32.dllenbcryptprimitives.dllte gebruiken (die soms falen door DLL-laadfouten of onnodige allocaties), kan Zig directNtOpenFileop\Device\CNGgebruiken. - NtReadFile en NtWriteFile: De
ReadFilewrapper inkernel32.dllverbergt statuscodes en gebruiktOVERLAPPEDtypes. De directeNtReadFileAPI geeft de foutcode direct terug als returnwaarde. Bovendien stelt het Zig in staat om APC-routines te gebruiken in plaats van events, wat naadloos integreert met annuleringen van taken.
---
31 januari 2026: zig libc
Auteur: Andrew Kelley
Het zig libc subproject heeft als doel om redundante code te verwijderen door libc-functies aan te bieden als Zig-wrappers in plaats van als vendored C-bronbestanden.
- Status: Ongeveer 250 C-bronbestanden zijn reeds verwijderd.
- Voordelen: Onafhankelijkheid van de C-taal, snellere compilatie, kleinere installatiegrootte en kleinere binaries bij statisch linken.
- Optimalisatie: Door de libc-functies te delen in de Zig Compilation Unit (ZCU), kunnen functies samen worden geoptimaliseerd (vergelijkbaar met LTO, maar in de frontend).
Dit biedt potentieel om in de toekomst I/O van libc-aanroepen te sturen naar bijvoorbeeld een io_uring event loop.
Groetjes,