Zig - General-Purpose Programming Language
Install and use Zig, a general-purpose programming language and toolchain designed for maintaining robust, optimal, and reusable software — covering installation, language features, build system, C interoperability, and best practices.
- Step 1
Overview
Zig is a general-purpose programming language and toolchain for maintaining robust, optimal, and reusable software. It is designed to be simple, provide explicit and predictable behavior, and eliminate hidden control flow. Zig compiles to native code and can interoperate seamlessly with C code.
Key Features:
- No Hidden Control Flow: No exceptions, no implicit allocations, no virtual method tables
- Manual Memory Management: Full control over memory with optional garbage collection
- C Interoperability: Import C headers directly, link with C libraries
- Compile-Time Code Execution: Run Zig code at compile-time for code generation
- No Preprocessor: Everything is in the language itself
- Undefined Behavior Avoidance: Safe by default, explicit unsafe blocks
- Native Build System: Built-in build system, no separate tool needed
- Package Manager: Native package management with dependency resolution
- Cross-Compilation: Built-in cross-compilation for multiple platforms
- Performance: Comparable to C/C++ with zero-cost abstractions
Philosophy:
- Simplicity: Small language surface, easy to learn and understand
- Pragmatism: Solves real-world problems without academic purity
- Optimality: Generates efficient code without hidden costs
- Maintainability: Easy to refactor, no hidden behavior
Official Docs: https://ziglang.org/documentation/master/ Documentation (Unofficial): https://ziglearn.org GitHub (Mirror): https://github.com/ziglang/zig (43K+ stars) Codeberg (Primary): https://codeberg.org/ziglang/zig (5K+ stars) Zig Packages: https://zigls.dev Zig Standard Library: https://github.com/ziglang/zig/tree/master/lib/std - Step 2
Installation
Zig can be installed via package managers, pre-built binaries, or from source. The self-hosted compiler (Zig 0.11+) is now stable and recommended.
Installation methods:
- Package managers (recommended for most users)
- Pre-built binaries from Zig website
- Build from source (for latest development version)
# Option 1: Package Managers (Recommended) # macOS (Homebrew) brew install zig # Linux (Nix) nix-shell -p zig # Linux (Snap) sudo snap install zig --classic # Windows (Scoop) scoop install zig # Windows (Chocolatey) choco install zig # Option 2: Pre-built Binaries # Download from: https://ziglang.org/download/ # Or use the installer script: # macOS/Linux wget https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz tar xf zig-linux-x86_64-0.13.0.tar.xz sudo mv zig-linux-x86_64-0.13.0 /usr/local/lib/zig sudo ln -s /usr/local/lib/zig/zig /usr/local/bin/zig # Windows # Download ZIP from ziglang.org/download # Extract to C:\ zig # Add C:\zig to PATH # Option 3: Build from Source (Requires Zig 0.11+ to build) git clone https://github.com/ziglang/zig.git cd zig make sudo make install # Verify installation zig version # Should print: zig version X.Y.Z - Step 3
First Zig Program
Create and run your first Zig program. Zig has a built-in build system that handles compilation and linking.
# Create project directory mkdir hello_zig cd hello_zig # Create main.zig cat > main.zig << 'EOF' const std = @import("std"); pub fn main() !void { const stdout = std.io.getStdOut().writer(); try stdout.print("Hello, Zig!\n", .{}); } EOF # Compile and run zig run main.zig # Output: Hello, Zig! # Compile to executable zig build-exe main.zig -o hello ./hello # Compile with optimizations zig build-exe main.zig -O ReleaseFast -o hello_opt # Compile for different targets zig build-exe main.zig -target x86_64-windows -o hello.exe zig build-exe main.zig -target wasm31-freestanding -o hello.wasm - Step 4
Build System
Zig has a built-in build system that replaces make, cmake, and other build tools. The build.zig file defines your project structure, dependencies, and build steps.
# Initialize a new project with build.zig zig init # Creates: # my_project/ # ├── build.zig # Build configuration # ├── build.zig.zon # Package manifest # ├── src/ # │ └── main.zig # Main source file # └── test/ # └── main.zig # Tests # Example build.zig: const std = @import("std"); pub fn build(b: *std.Build) void { // Target platform const target = b.standardTargetOptions(.{}); // Optimization level const optimize = b.standardOptimizeOption(.{}); // Main library const lib = b.addExecutable(.{ .name = "my_app", .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); // Install executable b.installArtifact(lib); // Add test step const unit_tests = b.addTest(.{ .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); const run_unit_tests = b.addRunArtifact(unit_tests); // Build steps const build_step = b.step("build", "Build application"); build_step.dependOn(&lib.step); const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&run_unit_tests.step); } # Build commands zig build # Build default step zig build test # Run tests zig build -Doptimize=ReleaseFast # Optimized build zig build -Dtarget=x86_64-windows # Cross-compile - Step 5
Language Basics
Zig has a C-like syntax with modern features. Variables are immutable by default, and types must be explicit.
// Variable declarations const PI = 3.14159; // Compile-time constant var count: u32 = 0; // Mutable variable (u32 = unsigned 32-bit int) const name = "Zig"; // Type inferred as []const u8 // Type system var a: i32 = 42; // Signed 32-bit integer var b: f64 = 3.14; // 64-bit float var c: bool = true; // Boolean var d: u8 = 'A'; // Unicode codepoint (ASCII char) // Arrays and slices const arr: [3]i32 = .{ 1, 2, 3 }; // Fixed-size array const slice = arr[0..2]; // Slice (dynamic view) // Structs const Point = struct { x: f64, y: f64, fn distance(self: Point, other: Point) f64 { const dx = other.x - self.x; const dy = other.y - self.y; return @sqrt(dx * dx + dy * dy); } }; const p1 = Point{ .x = 0, .y = 0 }; const p2 = Point{ .x = 3, .y = 4 }; const dist = p1.distance(p2); // Returns 5.0 // Enums enum Color { red, green, blue, } const c: Color = .red; // Switch expressions fn colorName(color: Color) []const u8 { return switch (color) { .red => "Red", .green => "Green", .blue => "Blue", }; } - Step 6
Error Handling
Zig uses explicit error unions instead of exceptions. Errors must be handled at every call site using the
tryoperator orerrdefer.// Error unions fn readFile(path: []const u8) ![]u8 { // Returns either []u8 (success) or an error return allocator.alloc(u8, size); } // Using try to propagate errors fn processFile(path: []const u8) !void { const data = try readFile(path); // Propagates error defer allocator.free(data); // Cleanup on function exit // Process data... } // Error handling with catch const result = riskyOperation() catch |\err| switch (err) { error.OutOfMemory => return 0, error.FileNotFound => return 1, else => return 2, }; // errdefer for cleanup fn allocateAndUse() !void { const ptr = try allocator.alloc(u8, 100); errdefer allocator.free(ptr); // Runs if any error after this try doSomething(ptr); // If this fails, errdefer runs // If we reach here, no error occurred allocator.free(ptr); // Manual cleanup } // else defer for normal cleanup fn safeOperation() void { const ptr = allocator.alloc(u8, 100) orelse return; defer allocator.free(ptr); // Always runs at function exit use(ptr); // ptr is freed here automatically } - Step 7
Memory Management
Zig provides manual memory management with allocator abstraction. There's no garbage collector, giving you full control over memory.
// General purpose allocator const std = @import("std"); pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); // Allocate memory const ptr = try allocator.alloc(u8, 1024); defer allocator.free(ptr); // Free on scope exit // Allocate with alignment const aligned = try allocator.alignedAlloc(u8, 16, 1024); defer allocator.alignedFree(aligned); // Create strings const str = try allocator.dupe(u8, "Hello"); defer allocator.free(str); // Automatic arena allocator (for scope-limited allocations) var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const arena_alloc = arena.allocator(); const data = try arena_alloc.alloc(u8, 1000); // No need to free - arena.deinit() frees everything // Page allocator (for large allocations) var page_allocator = std.heap.PageAllocator{}; defer page_allocator.deinit(); } - Step 8
C Interoperability
Zig can import C headers directly and link with C libraries. This is one of Zig's strongest features for gradual adoption.
// Import C header const c = @cImport({ @cInclude("stdio.h"), @cInclude("stdlib.h"), }); pub fn main() void { // Call C functions c.printf("Hello from C!\n"); // Use C types var num: c_int = 42; c.free(num); } // Link with C library zig build-exe main.zig -lc // Import generated bindings c const libc = @cImport({ @cInclude("libc.h"); // Zig provides stdlibc.zig }); // Use Zig's standard C bindings const std = @import("std"); const c = std.c; c.printf("Using std.c\n"); // Compile C code with Zig zig cc main.c -o program zig c++ main.cpp -o program zig ar rcs libfoo.a foo.o zig cppcheck file.c // C header generation zig translate-c foo.h -o foo.zig - Step 9
Compile-Time Execution
Zig can run code at compile-time using
comptime. This enables powerful metaprogramming without a preprocessor.// Compile-time constants const pi = @constEval(floatingPoint, .{ 3.14159 }); // Compile-time functions fn computeAtCompileTime(n: comptime usize) []const u8 { var result = ""; var i: usize = 0; while (i < n) : (i += 1) { result = result ++ "x"; } return result; } const repeated = computeAtCompileTime(5); // "xxxxx" computed at compile-time // Compile-time type generation fn Vector(comptime T: type, comptime dimensions: usize) type { return struct { data: [dimensions]T, fn init(self: *@This(), value: T) void { for (&self.data) |*d| * d.* = value; } }; } const Vec3 = Vector(f32, 3); // 3D vector of floats const Vec4 = Vector(f32, 4); // 4D vector of floats const Vec3Int = Vector(i32, 3); // 3D vector of integers // Compile-time conditionals if (comptime std.os.tag == .linux) { // Linux-specific code } else if (comptime std.os.tag == .windows) { // Windows-specific code } // Inline assembly at compile-time const asm_result = @asm("mov {0}, {1}", .{ result, value }); - Step 10
Standard Library
Zig's standard library provides utilities for I/O, collections, strings, math, and more. All code is visible and auditable.
// Import standard library const std = @import("std"); // I/O operations pub fn main() !void { const stdout = std.io.getStdOut().writer(); const stdin = std.io.getStdIn().reader(); try stdout.print("Enter name: ", .{}); var buffer: [100]u8 = undefined; const input = try stdin.readUntilDelimiterOrEof(&buffer, '\n'); try stdout.print("Hello, {}!\n", .{input}); // File operations const file = try std.fs.cwd().createFile("output.txt", .{}); defer file.close(); try file.write("Hello, File!\n"); // Reading files const content = try std.fs.cwd().readAllAlloc(std.heap.page_allocator, "input.txt"); defer std.heap.page_allocator.free(content); } // Collections const ArrayList = std.ArrayList; const StringHashMap = std.StringHashMap; const BinaryHeap = std.BinaryHeap; // String utilities const strings = std.mem.String; const ascii = std.ascii; // Hash const hash = std.hash; const sha256 = crypto.hash.sha2; // Math const math = std.math; const result = math.sqrt(f64, 16.0); // Testing test "addition" { const a = 5; const b = 3; try std.testing.expect(a + b == 8); } - Step 11
Testing
Zig has built-in testing support. Tests are run with
zig testand can be inline or in separate files.// Inline tests (same file) fn add(a: i32, b: i32) i32 { return a + b; } test "addition" { try std.testing.expect(add(2, 3) == 5); try std.testing.expect(add(-1, 1) == 0); } test "subtraction" { const result = sub(5, 3); try std.testing.expectEqual(@as(i32, 2), result); } // Table-driven tests test "arithmetic" { const tests = _[ .{ .a = 1, .b = 2, .expected = 3 }, .{ .a = 0, .b = 0, .expected = 0 }, .{ .a = -1, .b = 1, .expected = 0 }, ]; for (tests) |test_case| { try std.testing.expectEqual( test_case.expected, add(test_case.a, test_case.b) ); } } // Run tests zig test src/main.zig # Test single file zig build test # Run build test step zig test --summary all # Show all test results // Test coverage zig build-obj main.zig -fcoverage-sanitizer llvm-profdata merge -output=default.profdata default_%m_%p.profraw llvm-cov report main -instr-profile=default.profdata - Step 12
Package Management
Zig has a built-in package manager. Dependencies are declared in
build.zonand resolved automatically.# Package manifest (build.zon) { "name": "my-project", "version": "0.1.0", "path": "../", "dependencies": .{ "mustache": .{ "url": "https://github.com/ziglang/mustache.zig/archive/refs/tags/v1.0.0.tar.gz", "hash": "abc123...", }, }, } # In build.zig: const mustache = b.dependency("mustache", .{ ."version" = "~>1.0.0" }); lib.root_module.addImport("mustache", mustache.module(".")); # Add dependency from local path const local_dep = b.dependency("local", .{ .path = "../local-package", }); # Publish package # 1. Create build.zon with dependencies # 2. Upload source to GitHub/GitLab # 3. Reference in other projects via URL # Note: Zig package manager is still evolving # Check https://zigls.dev for available packages - Step 13
Cross-Compilation
Zig has built-in cross-compilation support. No need for separate toolchains or complex setup.
# List supported targets zig targets list # Cross-compile for Windows from Linux zig build-exe main.zig -target x86_64-windows -o app.exe # Cross-compile for macOS from Linux zig build-exe main.zig -target x86_64-macos -o app # Cross-compile for ARM zig build-exe main.zig -target armv7a-none-linux-gnueabihf -o app_arm # Cross-compile for WebAssembly zig build-exe main.zig -target wasm31-freestanding -o app.wasm # Check available ABIs zig targets list | grep windows # Create toolchain for specific target zig archiver create-toolchain x86_64-linux-gnu # In build.zig: const target = b.target( .{ .cpu_arch = .x86_64, .os_tag = .windows, .abi = .gnu, } ); - Step 14
Performance Optimization
Zig provides multiple optimization levels and tools for profiling and optimizing your code.
# Optimization levels zig build-exe main.zig -O Debug # No optimization, debug info zig build-exe main.zig -O ReleaseSafe # Optimizations, some checks zig build-exe main.zig -O ReleaseFast # Maximum speed optimizations zig build-exe main.zig -O ReleaseSmall # Size optimizations # Link-time optimizations zig build-exe main.zig -O ReleaseFast -flto # Strip symbols zig build-exe main.zig -O ReleaseFast -fstrip=all # Profile-guided optimization zig build-exe main.zig -fprofile-generate ./app # Run to collect profile zig build-exe main.zig -fprofile-use # Build with profile # Performance analysis cachegrind (Valgrind) for cycle counting perf for Linux performance analysis flamegraph for visualization // In code: use @call(.fast) for inlining const result = @call(.fast, func, .{ arg1, arg2 }); // Use @compileLog for debugging compile-time @compileLog("Value: ", value, "\n"); - Step 15
Best Practices
Learn common patterns, anti-patterns, and best practices for writing idiomatic Zig code.
## Common Patterns 1. Use `try` for error propagation, not `catch` everywhere 2. Use `defer` for cleanup, `errdefer` for error cleanup 3. Prefer `comptime` for compile-time computations 4. Use allocators explicitly, don't hide allocations 5. Make variables `const` by default, `var` when needed 6. Use slices instead of arrays when size is dynamic 7. Use `std.testing.expectEqual` for readable tests ## Anti-Patterns 1. Don't use `undefined` without initializing 2. Don't ignore errors with `catch {}` 3. Don't use `@as()` for unsafe casts 4. Don't create memory leaks with forgotten `defer` 5. Don't use `@compileLog()` in production code ## Code Organization - Put public functions at the top of files - Use `fn` for public, `const fn` for comptime functions - Group related types in structs - Use namespaces (structs with only static methods) - Separate platform-specific code with comptime checks ## Documentation /// This is a function doc comment fn myFunction(arg: Type) ReturnType { // Implementation } // Inline comments explain implementation details const value = 42; // Magic number for XYZ - Step 16
Resources
Complete list of official and community resources for learning and using Zig.
## Official Resources Zig Website: https://ziglang.org Documentation: https://ziglang.org/documentation/master/ Language Reference: https://ziglang.org/documentation/master/#Language-Reference Build System: https://ziglang.org/documentation/master/#Build-System GitHub (Mirror): https://github.com/ziglang/zig Codeberg (Primary): https://codeberg.org/ziglang/zig Discord: https://discord.gg/zig Mailing List: https://lists.ziglang.org ## Learning Resources Learn Zig: https://ziglearn.org Zig Book (Unofficial): https://zigbook.readthedocs.io Zig in Depth: https://ziglex.org Zig By Example: https://github.com/zig-examples Awesome Zig: https://github.com/cornelk/awesome-zig ## Community Zig Forums: https://zigforum.org Stack Overflow: https://stackoverflow.com/questions/tagged/zig Zig Packages: https://zigls.dev Zig Standard Library: https://github.com/ziglang/zig/tree/master/lib/std ## Tools zls (Zig Language Server): https://github.com/ziglang/zls zigfmt (Formatter): Built into Zig compiler zig repl: Interactive REPL (experimental) ## Tutorials Zig Crash Course: https://zigcrashcourse.com Zig in 100 Seconds: Various YouTube channels Building a Game in Zig: https://gamedevguide.com
Feature requests
Sign in to suggest features or vote on existing ones.
No feature requests yet.
Discussion
Sign in to join the discussion.
No comments yet.