Skip to content

Importing Files

Large programs are divided into smaller files. Zig uses @import to include declarations from another file.

Large programs are divided into smaller files. Zig uses @import to include declarations from another file.

Suppose a directory contains these files:

main.zig
math.zig

The file math.zig contains:

pub fn add(a: i32, b: i32) i32 {
    return a + b;
}

pub fn sub(a: i32, b: i32) i32 {
    return a - b;
}

Another file may import it:

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

pub fn main() void {
    const x = math.add(10, 20);
    const y = math.sub(50, 8);

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

The output is:

30 42

The expression:

@import("math.zig")

loads another Zig source file.

The result is a namespace-like object containing the file’s public declarations.

The name:

math

is chosen by the programmer. Any valid identifier may be used:

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

Then:

m.add(1, 2)

calls the imported function.

Imports are resolved at compile time.

If the imported file contains an error, compilation fails even if the faulty declaration is never used.

Only public declarations are visible:

fn helper() void {}

pub fn work() void {
    helper();
}

The caller may use:

module.work();

but not:

module.helper()

because helper is private.

Imports may also be nested.

Suppose:

main.zig
util/
    math.zig

Then:

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

imports the file.

Relative paths are commonly used for local modules.

A file may import many modules:

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

Each imported file has its own declarations and private state.

Imports are declarations like any other:

const std = @import("std");

Here std is simply a constant whose value comes from the import expression.

This means imports can appear inside functions:

pub fn main() void {
    const std = @import("std");

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

although imports are usually placed near the top of the file.

Importing a file does not copy source text into another file, as with the C preprocessor #include.

Zig imports declarations semantically. This avoids many problems common in textual inclusion systems.

A Zig file is itself a module.

For example:

pub const version = "0.16";

makes version accessible through imports.

Modules may expose constants, variables, functions, types, and tests.

Imported declarations are accessed with dot notation:

math.add(1, 2)
std.debug.print(...)

This keeps namespaces explicit and avoids many name collisions.

A common style is:

const std = @import("std");

in almost every source file.

The standard library is large, but importing std does not automatically include all of it in the final executable. Zig compiles only what is used.

Exercise 4-17. Split a calculator program across two files.

Exercise 4-18. Create a module geometry.zig with public functions area and perimeter.

Exercise 4-19. Import the standard library inside a function instead of at file scope.

Exercise 4-20. Why is Zig’s import system different from C’s #include model?