zigvet

Things Zig compiles that are wrong.

zigvet reads your .zig files with std.zig.Ast, the compiler’s own parser, and reports code the compiler accepts in every build mode that is almost certainly a bug: a slice of a dead stack frame handed back to the caller, a double free on an error path, a lock still held after a failure, a buffered writer never flushed, tests that never run. go vet is the closest analog: not style, which zig fmt and the existing linters cover, but silent wrongness.

Install The rules GitHub

  • Twenty-two rules. Eighteen have found real bugs: in Zig’s own standard library and compiler, in well-known open-source Zig projects, and in application code.
  • Syntax only. No build, no configuration. It reads Zig’s standard library and compiler, 650,000 lines, in under a second.
  • Quiet when there is nothing to say, as zig fmt --check is, and fails the build when there is.

A quick look

pub fn scopesFor(scope: Scope, asked: []const []const u8) []const []const u8 {
    return if (scope == .monitoring) &.{Scope.monitoring.url()} else asked;
}
$ zigvet src
src/auth.zig:14:38: escape: anonymous array made here leaves scopesFor through return
    hint: make it comptime-known (`comptime` on the runtime elements) or a container-level const

Scope.monitoring.url() is a call, so the array is built at run time, in scopesFor’s own frame, and the slice it returns points into memory the next call writes over. Zig 0.17.0 compiles this without a diagnostic in every mode. This exact code shipped, passed its tests for two days, and failed only when an unrelated std.debug.print happened to overwrite the stack slot. The fix is one word: &.{comptime Scope.monitoring.url()}. More on escape.

Install

Where How
macOS, Linux brew install kmoneil/tap/zigvet
Windows scoop bucket add kmoneil https://github.com/kmoneil/scoop-bucket then scoop install kmoneil/zigvet
Anywhere a binary from the latest release, for Linux, macOS and Windows on x86-64 and arm64
From source zig build -Doptimize=ReleaseSafe with Zig 0.17.1, which leaves it in zig-out/bin/

Every release file comes with SHA256SUMS and a build-provenance attestation, and releases build reproducibly; the README shows how to check both.

Run it

zigvet .                          # every .zig file under here
zigvet --summary src              # and say what was checked
zigvet --format github src        # annotate a pull request in CI

It exits 0 when it reports nothing, 1 when it reports something, and 2 on a usage error. Usage has the options, the output formats, allowing a report, and a lint step for build.zig.

The rules

Each rule is one word for what it catches. The rules page shows them all with what goes wrong.