Skip to content

Public Declarations

A Zig program may be split across many files. Declarations can be made visible outside a file with pub.

A Zig program may be split across many files. Declarations can be made visible outside a file with pub.

Here is a small file named math.zig:

pub fn square(x: i32) i32 {
    return x * x;
}

The function is declared with:

pub fn

which means the declaration is public.

Another file may import and use it:

const std = @import("std");
const math = @import("math.zig");

pub fn main() void {
    const n = math.square(5);

    std.debug.print("{d}\n", .{n});
}

The output is:

25

The declaration:

const math = @import("math.zig");

imports the file and binds it to the name math.

The function is called with:

math.square(5)

The dot selects a declaration from the imported file.

Declarations without pub are private:

fn helper(x: i32) i32 {
    return x + 1;
}

This function may only be used inside its own file.

The following file is valid:

fn helper(x: i32) i32 {
    return x + 1;
}

pub fn compute(x: i32) i32 {
    return helper(x) * 2;
}

compute is public. helper is private implementation detail.

This is a common pattern.

A file in Zig acts much like a namespace. Public declarations form the visible interface.

Variables may also be public:

pub const version = "0.16";

Another file may access it:

const config = @import("config.zig");

const v = config.version;

Types may be public too:

pub const Point = struct {
    x: i32,
    y: i32,
};

The imported type is used normally:

const geometry = @import("geometry.zig");

const p = geometry.Point{
    .x = 3,
    .y = 4,
};

The name after pub becomes part of the module interface.

Good interfaces expose only what users need.

This file exposes too much:

pub fn parse() void {}
pub fn scan() void {}
pub fn tokenize() void {}
pub fn internalBuffer() void {}
pub fn tempState() void {}

Internal details become difficult to change later.

A better design hides implementation details:

fn scan() void {}
fn tokenize() void {}

pub fn parse() void {
    scan();
    tokenize();
}

Only the high-level operation is public.

Public declarations also affect documentation and discoverability. A clean public interface makes libraries easier to understand.

Imports are resolved at compile time. Zig does not have a separate header language like C.

The file itself defines both implementation and interface.

This keeps declarations synchronized. There is no separate header file that can drift out of date.

Circular imports should generally be avoided:

a.zig imports b.zig
b.zig imports a.zig

This often indicates poor structure.

Instead, move shared declarations into a third file.

Public declarations are commonly grouped near the top of a file:

pub const Point = struct {
    x: i32,
    y: i32,
};

pub fn distance() void {}

fn helper() void {}

The public interface becomes easy to find.

Exercise 4-13. Create a file math.zig with public functions add and sub.

Exercise 4-14. Add a private helper function used by one of the public functions.

Exercise 4-15. Create a public struct Person with fields name and age.

Exercise 4-16. Why might exposing too many public declarations make a library harder to maintain?