12 KiB
12 KiB
Zig 0.16.0 stdlib API notes (verified against ../zig at tag 0.16.0)
Ground truth for build sub-agents. Every claim below was read from the 0.16.0 source
(commit 24fdd5b7a4c1c8b5deb5b56756b9dbc8e08c86a8). When in doubt, re-check the source at
/home/mokhtar/app/zig/lib/std/ — do not trust pre-0.16 knowledge; the std.Io migration
changed most of these APIs.
std.Build (build.zig)
b.addExecutable(.{ .name, .root_module, ... })— no target/optimize on the artifact. Target/optimize/link_libc go on the module:b.createModule(.{ .root_source_file, .target, .optimize, .link_libc, .imports, ... })(Module.CreateOptions, Module.zig:216).- All C configuration is module-level (Step.Compile has none of these methods):
exe.root_module.addCSourceFile(.{ .file, .flags, .language })exe.root_module.addCSourceFiles(.{ .root, .files, .flags })—filesmust be relative paths; absolute paths panic.exe.root_module.addIncludePath(lazy_path),.addCMacro(name, value)- libc:
link_libc = truein CreateOptions orexe.root_module.link_libc = true.
- Static lib:
b.addLibrary(.{ .linkage = .static, .name, .root_module })(b.addStaticLibrarydoes not exist). Link:exe.root_module.linkLibrary(lib). - Cross targets:
b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .musl })orstd.Target.Query.parse(.{ .arch_os_abi = "x86_64-linux-musl" }). musl → static libc; for a fully static binary also setexe.linkage = .static. - Options:
b.option(std.Build.LazyPath, "web-dist", "...")accepts LazyPath.b.addOptions()+opts.addOption/addOptionPath+exe.root_module.addOptions("build_options", opts). - Embedding an asset directory: one WriteFile step holding both a generated
index.zigand the assets:const wf = b.addWriteFiles(); _ = wf.addCopyDirectory(dist, "assets", .{}); const idx = wf.add("index.zig", src);thenexe.root_module.addAnonymousImport("assets", .{ .root_source_file = idx }).@embedFilepaths must stay inside that module root (error.ImportOutsideModulePath). - Version pin: compare
@import("builtin").zig_version(SemanticVersion) in comptime,@compileErroron mismatch. - build.zig.zon:
.nameis an enum literal (.nxdns),.fingerprintis required (compiler suggests the value on first run), plus.version,.paths,.dependencies(path/url+hash/lazy),.minimum_zig_version.
std.process + stdio (found during S1)
std.process.args/std.process.ArgIteratordo not exist in 0.16. Arguments arrive through main's parameter:pub fn main(init: std.process.Init) u8 { var args = init.minimal.args.iterate(); ... }.std.process.Initcarries.io,.gpa,.arena,.environ_map,.preopens,.minimal(.args,.environ). See lib/std/process.zig:30 and lib/std/process/Args.zig.- Console output:
std.Io.File.stdout().writer(io, &buffer)then use.interface(lib/std/Io/File.zig:91,600). Same shape for stderr. - Package deps unpack into
zig-pkg/inside the project root (gitignore it). - Entropy:
io.random(buf)(Io.zig:2468). Realtime timestamp:std.Io.Clock.real.now(io)→Io.Timestamp(Io.zig:778). There is NOio.now(.real). std.mem.indexOfScalaris gone; usestd.mem.findScalar(mem.zig:1219).- Conditional imports are impossible: no
@hasImport, and@import("root")in azig testbuild resolves to the compiler's test runner, never your tests root. Integration tests gated onbuild_optionstherefore live in a separate file with a runtimeif (!build_options.integration) return error.SkipZigTest;guard (body stays semantically analyzed either way — code cannot rot). - ENVIRONMENT (this dev machine): IPv6 egress is broken — hostname-resolving tests hit AAAA-first timeouts. Use documented IPv4 literals in live tests, keep SNI on the name.
zig fetchaccepts tar.gz/zip etc. but not .tar.bz2 (no bzip2 decompressor — src/Package/Fetch.zig ~line 1337). Pin GitHub source tag tarballs when a release asset is bz2-only.
std.Io (Threaded backend, concurrency)
var t = std.Io.Threaded.init(gpa, .{});— returned by value, must live at a stable address.const io = t.io();defer t.deinit();(joins workers). InitOptions:stack_size,async_limit(default cpus-1),concurrent_limit.io.async(f, args) Future(R)may run inline;io.concurrent(f, args) !Future(R)guarantees its own unit of concurrency — use for server loops.future.await(io),future.cancel(io).Io.Group:.init,g.async(io, f, args),g.concurrent(io, f, args),g.await(io),g.cancel(io). Group task fns must return something coercible toCancelable!void. Cancellation: next cancelable Io call returnserror.Canceledonce;io.recancel()re-arms;io.swapCancelProtection(.blocked)protects cleanup sections.- No SIGINT/SIGTERM handling in Threaded (only no-op SIGIO/SIGPIPE). Install our own
posix.sigaction, set an atomic flag, cancel the group from the main task.
std.Io.net
- UDP:
addr.bind(io, .{ .mode = .dgram })→Socket. Receive:sock.receive(io, buf) !IncomingMessage— fieldsfrom: IpAddress,data: []u8(slice into buf),flags.trunc. Timeout variant:receiveTimeout(io, buf, timeout). Send:sock.send(io, &dest_addr, data). Close:sock.close(io). (No recvFrom/sendTo names.) - TCP server:
addr.listen(io, .{ .reuse_address = true })→net.Server;srv.accept(io) !Stream;srv.deinit(io).Stream.close(io),.shutdown(io, how). Shutdown of the listener makes a blocked accept failerror.SocketNotListening. - TCP client:
addr.connect(io, .{ .mode = .stream })→Stream—ConnectOptions.modeis REQUIRED (no default, net.zig:332).ConnectOptions.timeoutis a LANDMINE in 0.16.0: the Threaded backend panics "TODO implement netConnectIpPosix with timeout" (Threaded.zig:12077). Never set it. Bound connects the same way as reads: race the task against aClock.Duration.sleepviastd.Io.Selectand cancel the loser (see tls_client_integration_test.zig). - Stream reads/writes accept no timeout in 0.16.0 (VTable netRead/netWrite have none;
Operation lacks net stream variants). Bound a TCP/TLS read by running it under
io.concurrentand cancelling the future (or shutdown the socket). - Reader/Writer:
stream.reader(io, buf) Stream.Reader; generic interface is&stream_reader.interface(*Io.Reader); same for writer. Caller owns buffers;&.{}legal. Concrete errors land in.err; interface returns ReadFailed/WriteFailed.Stream.Writer.sendFilereturnserror.Unimplementedin 0.16.0. - Known: net tests in stdlib are skipped (upstream issue 31388) — do not copy test.zig patterns blindly.
std.http.Client (DoH upstream)
- Construct by struct literal:
var client: std.http.Client = .{ .allocator = gpa, .io = io };defer client.deinit();Fields:ca_bundle,ca_bundle_lock,now: ?Io.Timestamp,connection_pool(LRU, free_size 32),read_buffer_size,write_buffer_size. - DoH POST flow (low-level;
fetchhides the body—don't use it):var req = try client.request(.POST, uri, .{ .headers = .{ .content_type = .{ .override = "application/dns-message" } } });try req.sendBodyComplete(body_mut);(body is[]u8, sets content-length + flush)var resp = try req.receiveHead(&redirect_buf);- Check
resp.head.status,resp.head.content_type. const rdr = resp.reader(&transfer_buf);then read (allocRemaining/readSliceShort).req.deinit()returns the connection to the pool.
- TLS: client TLS uses std.crypto.tls.Client internally with the client's
ca_bundle; lazilyrescans system roots whenclient.now == null(error.CertificateBundleLoadFailureon failure). Pre-fillca_bundle+ setnowto skip the scan. - No per-request deadline exists. Enforce total-budget timeouts via concurrent+cancel (see std.Io note above).
std.http.Server (web/API/DoH server)
var srv = std.http.Server.init(&reader.interface, &writer.interface);Loop for keep-alive:while (srv.reader.state == .ready)+ catch HttpConnectionClosing.var req = try srv.receiveHead();—req.head.method/.target/.content_type/...,req.iterateHeaders(). Copyhead.targetbefore reading the body (body reads invalidate head memory). Body:req.readerExpectContinue(buf)(handles 100-continue) orreq.readerExpectNone(buf).- Simple response:
try req.respond(content, .{ .status = ..., .extra_headers = &.{...} }); - Streaming/SSE:
var body = try req.respondStreaming(&.{}, .{ .respond_options = ... });Withcontent_length == null→ chunked. Use an empty buffer so each write drains through; thenresponse.flush()after each event suffices;response.end()to finish. (With a non-empty buffer you must flush response.writer first — trap.) - WebSocket upgrade exists (
upgradeRequested/respondWebSocket) — unused by nxdns. - No MIME table, no etag: set
content-type/cache-controlmanually via extra_headers. BodyWriter supports sendFile (contentLengthSendFile/chunkedSendFile).
std.testing.fuzz (0.16)
std.testing.fuzz(context, testOne, options)wheretestOne: fn(ctx, smith: *std.testing.Smith) anyerror!void— the input is a*Smithstructured generator, NOT a raw[]const u8slice (pre-0.16 form is gone).FuzzInputOptions{ .corpus: []const []const u8 }seeds the corpus.- Smith surface (lib/std/testing/Smith.zig):
valueWithHash,valueRangeAtMostWithHash,bytesWithHash,sliceWithHash(buf, hash) u32(fills buf, returns length),eosWithHash, etc. For raw-bytes parsers: fill a buffer viasliceWithHashand feed the prefix to the parser. - Plain
zig build testreplays each corpus entry once + one empty input (deterministic).--fuzz=<n>= iterations PER TEST (K/M/G suffixes); bare--fuzz= forever + webui; no time bound exists;--fuzz=<n>conflicts with--webui. - Corpus entries are Smith byte streams (slice = u32-LE length + bytes), not raw inputs.
- STOCK 0.16.0 CANNOT RUN
--fuzz: (A) fuzz-mode test_runner.zig:566 has a type error (needs patched --zig-lib-dir); (B) the self-hosted x86_64 backend emits no coverage PCs — set.use_llvm = trueon the fuzz test artifact. zig testcollects tests ONLY from the root module — moving files into a named module silently drops their tests. A file belongs to exactly one module per compilation, and imports cannot escape the module root directory. Hence: aggregator imports files directly; separate artifacts import a module-root file (e.g. src/dns/dns.zig).
std.zon
- Parse:
std.zon.parse.fromSlice(T, gpa, src_z, ?*Diagnostics, .{})for pointer-free T (no free needed);fromSliceAlloc+std.zon.parse.free(gpa, v)for slice-bearing T. Source must be[:0]const u8. No reader-based entry point. Options:ignore_unknown_fields,free_on_error. Diagnostics: init.{},deinit(gpa), print with{f}. - Struct default field values apply for absent fields. Tagged unions:
.foo(void) or.{ .foo = v }. Enums, optionals (single level only —??Trejected), slices, arrays, nested structs all fine. Error sets/unions, many-pointers, comptime_int rejected. - Serialize:
std.zon.stringify.serialize(value, .{ .whitespace = true }, writer). Streaming:var s: std.zon.Serializer = .{ .writer = w, .options = .{} };withbeginStruct/field/end.
std.crypto.tls.Client (upstream DoT)
std.crypto.tls.Client.init(input: *Io.Reader, output: *Io.Writer, options) !ClientOptions:.host = .{ .explicit = "dns.google" }(or.no_verification),.ca = .{ .bundle = .{ .gpa, .io, .lock, .bundle } }(or.no_verification/.self_signed),.write_buffer,.read_buffer(input buffer ≥Client.min_buffer_len=tls.max_ciphertext_record_len),.entropy: *const [240]u8(fill viaio.random),.realtime_now: Io.Timestamp.- After init: plaintext via
&client.reader/&client.writer(held by value — the Client must not move after init).client.end()sends close_notify. Errors inclient.read_err/client.alert. - TLS 1.2/1.3. No ALPN, no session resumption (fine for DoT; DoH over HTTP/1.1 works without ALPN in practice — verify against real upstreams in Phase 3).
- CA roots:
std.crypto.Certificate.Bundle(note the path),bundle.rescan(gpa, io, now).