<a id="use-zig"></a>

# Develop with Zig on Ubuntu

This tutorial shows how to create, build, run, and test a Zig program on Ubuntu. For instructions on how to install Zig and set up editor tooling, see the dedicated guide on [How to set up a development environment for Zig on Ubuntu](https://documentation.ubuntu.com/ubuntu-for-developers/howto/zig-setup.md#install-zig). This article assumes that tooling suggested in that article has been installed.

## Creating a Zig project

1. Create a project directory and change into it:
   `dev@ubuntu:~$ ``mkdir -p ~/zig/hello && cd ~/zig/hello`
2. Initialize the project:
   `dev@ubuntu:~/zig/hello$ ``zig init`

   `zig init` creates the following project structure:
   `dev@ubuntu:~/zig/hello$ ``tree`
   ```text
   .
   ├── build.zig
   ├── build.zig.zon
   └── src
       ├── main.zig
       └── root.zig
   ```

   * `build.zig` - the build script, written in Zig itself
   * `build.zig.zon` - package manifest (name, version, dependencies)
   * `src/main.zig` - the executable entry point
   * `src/root.zig` - a library module stub
3. Inspect the generated `src/main.zig`:
   ```zig
   const std = @import("std");

   pub fn main() !void {
       std.debug.print("All your {s} are belong to us.\n", .{"codebase"});
   }
   ```

   #### NOTE
   `zig init` generates the `main.zig` file with copious comments, as well as other logic, including example tests and an import of the `hello_lib` library that was also created; above is just the bare minimum needed for the example program.

   The project would still compile and run if you stripped all the extra code and only left what we’re showing here.

## Building and running

1. Build and run the project using `zig build run`:
   `dev@ubuntu:~/zig/hello$ ``zig build run`
   ```text
   All your codebase are belong to us.
   ```
2. Replace the message in `src/main.zig` with ‘Hello, world!’:
   ```zig
   const std = @import("std");

   pub fn main() !void {
       std.debug.print("Hello, world!\n", .{});
   }
   ```
3. Build and run again:
   `dev@ubuntu:~/zig/hello$ ``zig build run`
   ```text
   Hello, world!
   ```

## Compiling a single file

Use `zig build-exe` to compile a single source file without a project structure.

1. Create a standalone source file:
   ```zig
   const std = @import("std");

   pub fn main() !void {
       std.debug.print("Hello, world!\n", .{});
   }
   ```
2. Compile it:
   `dev@ubuntu:~/zig/hello$ ``zig build-exe hello.zig`

   This produces an executable named `hello` in the current directory.
3. Run the executable:
   `dev@ubuntu:~/zig/hello$ ``./hello`
   ```text
   Hello, world!
   ```

## Testing Zig code

Zig has a built-in test runner. Test blocks are declared with the `test` keyword and use the `std.testing` namespace for assertions.

The generated `src/root.zig` already contains an example test.

1. Review the `src/root.zig` example test:
   ```zig
   const std = @import("std");
   const testing = std.testing;

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

   test "basic add functionality" {
       try testing.expect(add(3, 7) == 10);
   }
   ```
2. Run the test:
   `dev@ubuntu:~/zig/hello$ ``zig build --summary all test`<div class="highlight-default notranslate"><div class="highlight"><pre><span style="color:#18b2b2;">Build Summary:</span><span style="color:#b2b2b2;"> 5/5 steps succeeded; 1/1 tests passed</span><span style="color:#b2b2b2;">
   </span><span style="color:#b2b2b2;">test</span><span style="color:#18b218;"> success</span><span style="color:#b2b2b2;">
   </span><span style="color:#b2b2b2;">├─ run test</span><span style="color:#18b218;"> 1 passed</span><span style="color:#656565;"> 635us MaxRSS:1M</span><span style="color:#b2b2b2;">
   </span><span style="color:#b2b2b2;">│ &#160;└─ zig test Debug native</span><span style="color:#18b218;"> success</span><span style="color:#656565;"> 1s MaxRSS:260M</span><span style="color:#b2b2b2;">
   </span><span style="color:#b2b2b2;">└─ run test</span><span style="color:#18b218;"> success</span><span style="color:#656565;"> 191us MaxRSS:1M</span><span style="color:#b2b2b2;">
   </span><span style="color:#b2b2b2;"> &#160;&#160;└─ zig test Debug native</span><span style="color:#18b218;"> success</span><span style="color:#656565;"> 1s MaxRSS:261M</span></pre></div></div>

### Testing STDOUT output

To test the actual “Hello, world!” function of the program, you can use a special feature of Zig: duck-typing at compile time. In this example, we create a `hello()` function that accepts any object that provides a `.print()` method.

This separates the output destination of the function from its logic, which makes it testable. In this way, the function can write to STDOUT, and the test is able to capture the output by directing it somewhere else where it can check it.

Modify the `src/main.zig` file:

```zig
const std = @import("std");

// Duck-typing the 'writer' parameter
pub fn hello(writer: anytype) !void {
    try writer.print("Hello, world!\n", .{});
}

// Sending output to STDOUT
pub fn main() !void {
    try hello(std.io.getStdOut().writer());
}

// Sending output to an array
test "hello writes 'Hello, world!' to stdout" {
    var output = std.ArrayList(u8).init(std.testing.allocator);
    defer output.deinit();
    try hello(output.writer());
    try std.testing.expectEqualStrings("Hello, world!\n", output.items);
}
```

#### NOTE
`defer` ensures that `output.deinit()` (which frees the allocated memory) is called automatically when the test ends (regardless of whether the function succeeds or not).

## Cross-compiling with `zig cc`

Zig includes a built-in C (Clang LLVM) compiler, `zig cc`, which works as a drop-in replacement for C compilers with first-class cross-compilation support. No separate cross-toolchain installation is required.

### Compiling C code natively

1. Create a C source file:
   ```c
   #include <stdio.h>

   int main(void) {
       printf("Hello, world!\n");
       return 0;
   }
   ```
2. Compile and run:
   `dev@ubuntu:~/zig/hello$ ``zig cc hello.c -o hello && ./hello`
   ```text
   Hello, world!
   ```

### Compiling for other architectures

The `zig cc` command compiles for any supported target without additional toolchain packages.

1. Cross-compile for 64-bit ARM Linux (statically linked against the [musl](https://musl.libc.org/) libc implementation):
   `dev@ubuntu:~/zig/hello$ ``zig cc hello.c -target aarch64-linux-musl -o hello-arm64`
2. Verify the binary targets ARM64:
   `dev@ubuntu:~/zig/hello$ ``file hello-arm64`
   ```text
   hello-arm64: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV),
                statically linked, not stripped
   ```
3. Install QEMU user-mode emulation:
   `dev@ubuntu:~/zig/hello$ ``sudo apt install qemu-user-binfmt`

   Ubuntu’s `qemu-user-binfmt` package registers `binfmt_misc` handlers automatically, so foreign-architecture binaries run transparently.
4. Run the Aarch64 executable:
   `dev@ubuntu:~/zig/hello$ ``./hello-arm64`
   ```text
   Hello, world!
   ```

#### NOTE
Similarly, cross-compile for 64-bit Windows and run with [Wine](https://www.winehq.org/):

`dev@ubuntu:~/zig/hello$ ``zig cc hello.c -target x86_64-windows-gnu -o hello.exe``dev@ubuntu:~/zig/hello$ ``sudo apt install wine``dev@ubuntu:~/zig/hello$ ``wine hello.exe`
```text
Hello, world!
```
