⚡ Zig Guide LiveUnofficialbut fully verified
✓ Zig 0.17.0-dev.2122+3e15e99e6What's newOn an older Zig?

Generating Documentation

Zig generates HTML documentation from doc comments.

zig build-lib src/root.zig -femit-docs
zig build docs        # if your build.zig declares it

The comment forms

SyntaxAttaches to
///the declaration that follows
//!the enclosing file/module (must be at the top)
//ordinary comment, not documentation
//! A module for working with points.

/// A point in two dimensions.
pub const Point = struct {
    /// Horizontal position, in pixels.
    x: i32,
};

Doc comments are Markdown, and only pub declarations appear in the output.

A /// comment in a position where it cannot attach to anything is a compile error, not a warning, so they cannot silently drift away from what they describe.

What comes out

The generator does one thing: it lifts the signature out of the source and puts your /// text underneath it.

Source

  /// Adds two integers.
  pub fn add(a: i32, b: i32) i32 { ... }


        │  zig build-lib -femit-docs


Generated page

  add(a: i32, b: i32) i32
  Adds two integers.

That picture is the whole argument of the next section. The signature is already there, generated from the code and correct by construction. Anything your comment spends on restating it is a line that can go stale while the generated half above it stays right.

In build.zig

const docs = b.addInstallDirectory(.{
    .source_dir = lib.getEmittedDocs(),
    .install_dir = .prefix,
    .install_subdir = "docs",
});
b.step("docs", "Generate documentation").dependOn(&docs.step);

What to write in one

The generated page already shows the signature, so repeating it in prose is waste. What it cannot show is everything a caller actually needs to know, and that is the list worth working through:

  • Who owns the return value. If it allocates, say which allocator frees it and when. This is the single most useful sentence in most doc comments.
  • What the errors mean. The error set is visible; what causes each member is not.
  • The constraints the type cannot express. That a slice must be sorted, that two lengths must match, that a pointer must stay alive for the call.
  • What it costs, when it is not obvious. O(n) versus O(1) changes how the function is used.

Applied to the example above, “Adds two integers” fails the test: the reader can see that from add(a: i32, b: i32) i32. What the signature cannot say is that the addition is checked and will panic on overflow rather than wrap. That sentence is worth writing; the other one is not.

Only pub appears

That makes the doc build a check on your interface. Generate the docs and you may find a type you meant to keep private, or a helper with no comment sitting in the public list. That is a review of the boundary that would otherwise never happen.

The output is a static site: HTML, plus the source, plus a search index. It needs no server and can be published anywhere files can be, which is what addInstallDirectory above is arranging.

Doctests

Because tests are ordinary language constructs, a test block next to a function serves as both a test and an example. Unlike a comment, it cannot go stale without the build noticing. That is the same principle this entire guide is built on.

Naming a test after the function it exercises, test myFunction { ... }, associates the two, so the example travels with the documentation rather than sitting in a separate file that nobody updates.

Treat a test as the primary form of documentation rather than a supplement to it. A prose example in a comment is a claim about how the function behaves, unchecked, written against whatever the signature was on the day it was typed. A test making the same claim is checked on every build. When the two disagree, and eventually they do, the test is the one that is right.