Skip to main content

Imports

Zig uses the @import built-in to bring code from other files:
const std = @import("std");
const print = std.debug.print;

pub fn main() void {
    print("Hello, World!\n", .{});
}
@import returns a struct representing the imported file, with all public declarations as fields.

Standard Library Import

The standard library is imported with @import("std"):
const std = @import("std");
const ArrayList = std.ArrayList;
const testing = std.testing;
const mem = std.mem;
The standard library provides modules for memory, collections, I/O, networking, and much more.

File Imports

Import other files in your project:
// main.zig
const utils = @import("utils.zig");
const parseU64 = @import("parser.zig").parseU64;

pub fn main() void {
    const result = utils.add(5, 3);
    _ = result;
}
// utils.zig
pub fn add(a: i32, b: i32) i32 {
    return a + b;
}

fn privateFunction() void {
    // Not accessible from other files
}

Exports

Public Declarations

Use pub to make declarations visible to importing files:
// math.zig
pub fn add(a: i32, b: i32) i32 {
    return a + b;
}

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

pub const PI = 3.14159;

// Private - not accessible from imports
fn internalHelper() void {}
pub fn publicFunction() void {}
pub const PUBLIC_CONSTANT = 42;
pub const PublicStruct = struct {};

Exporting for C

Use export to make symbols available for C linking:
export fn add(a: i32, b: i32) i32 {
    return a + b;
}
This creates a C-compatible symbol that can be called from C code.
export uses C ABI and naming. Use it only for C interoperability.

Package Structure

A typical Zig project structure:
my-project/
├── build.zig
├── build.zig.zon
├── src/
│   ├── main.zig
│   ├── lib.zig
│   ├── utils/
│   │   ├── math.zig
│   │   └── string.zig
│   └── core/
│       └── types.zig
└── tests/
    └── tests.zig

Build System

The build.zig file defines how to build your project:
const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "my-app",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    b.installArtifact(exe);
}

Dependencies

Manage dependencies in build.zig.zon:
.{
    .name = "my-project",
    .version = "0.1.0",
    .dependencies = .{
        .@"some-package" = .{
            .url = "https://example.com/some-package/archive/v1.0.0.tar.gz",
            .hash = "1220...",
        },
    },
}
Then import in your code:
const some_package = @import("some-package");
Zig’s package manager is built into the build system and uses content hashing for reproducibility.

Module Organization

Single File Module

// math.zig
pub fn add(a: i32, b: i32) i32 {
    return a + b;
}

pub fn multiply(a: i32, b: i32) i32 {
    return a * b;
}

Multi-File Module

// utils.zig (module root)
pub const math = @import("utils/math.zig");
pub const string = @import("utils/string.zig");

// Usage:
const utils = @import("utils.zig");
const result = utils.math.add(1, 2);

Container-Level Variables

Modules can have container-level state:
// config.zig
var log_level: LogLevel = .info;

pub fn setLogLevel(level: LogLevel) void {
    log_level = level;
}

pub fn getLogLevel() LogLevel {
    return log_level;
}
Container-level mutable state is shared across all uses of the module. Use carefully.

Circular Dependencies

Zig handles circular dependencies:
// a.zig
const b = @import("b.zig");

pub fn funcA() void {
    b.funcB();
}
// b.zig
const a = @import("a.zig");

pub fn funcB() void {
    // Can reference a.funcA
}

Builtin Module

Access compiler information:
const builtin = @import("builtin");
const std = @import("std");

pub fn main() void {
    std.debug.print("OS: {}\n", .{builtin.os.tag});
    std.debug.print("Arch: {}\n", .{builtin.cpu.arch});
    std.debug.print("Mode: {}\n", .{builtin.mode});
}
const builtin = @import("builtin");

const is_windows = builtin.os.tag == .windows;
const is_linux = builtin.os.tag == .linux;
const is_macos = builtin.os.tag == .macos;

Namespace Organization

Organize code with nested structs:
pub const math = struct {
    pub const constants = struct {
        pub const PI = 3.14159;
        pub const E = 2.71828;
    };
    
    pub fn add(a: i32, b: i32) i32 {
        return a + b;
    }
};

// Usage:
const pi = math.constants.PI;
const sum = math.add(1, 2);

Testing Modules

Tests can be organized in separate files:
// src/math.zig
pub fn add(a: i32, b: i32) i32 {
    return a + b;
}

test "add" {
    const std = @import("std");
    try std.testing.expect(add(1, 2) == 3);
}
// tests/math_test.zig
const math = @import("math");
const std = @import("std");

test "comprehensive add tests" {
    try std.testing.expect(math.add(0, 0) == 0);
    try std.testing.expect(math.add(-1, 1) == 0);
    try std.testing.expect(math.add(100, 200) == 300);
}
Run tests with zig test src/main.zig or zig build test.

Library Root File

For libraries, create a root file that exports all public APIs:
// lib.zig
pub const math = @import("math.zig");
pub const string = @import("string.zig");
pub const collections = @import("collections.zig");

pub const version = "1.0.0";

test {
    // Include all tests
    @import("std").testing.refAllDecls(@This());
}

Best Practices

  • Use pub to explicitly mark public APIs
  • Organize related functionality into modules
  • Create a root module file for libraries
  • Use the builtin module for platform detection
  • Keep modules focused and cohesive
  • Use namespaces (nested structs) for organization
  • Document public APIs with doc comments
  • Include tests in the same file as implementation when appropriate
Zig’s module system is simple: files are modules, and @import brings them together. There are no complex visibility rules or module declarations.

Build docs developers (and LLMs) love