# Windows Support

### Windows Support

Windows is one of Zig’s main supported platforms. You can write Zig programs on Windows, build Windows executables, call Windows system APIs, link with C libraries, and cross-compile Windows programs from Linux or macOS.

For beginners, the most important idea is simple: Zig does not treat Windows as a second-class target. Windows support is part of the normal Zig workflow.

You can write:

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

pub fn main() void {
    std.debug.print("Hello from Windows!\n", .{});
}
```

Then build it:

```bash
zig build-exe main.zig
```

On Windows, this produces an `.exe` file:

```text
main.exe
```

That file can be run directly from PowerShell, Command Prompt, Windows Terminal, or another shell.

```powershell
.\main.exe
```

#### Windows Is Different from Unix

Many beginner programmers first learn programming on Linux-like systems. But Windows has its own rules.

Paths are different:

```text
C:\Users\alice\Desktop\file.txt
```

Command-line shells are different:

```powershell
.\program.exe
```

Newline conventions can be different:

```text
\r\n
```

System APIs are different. Linux has POSIX APIs such as `fork`, `exec`, and file descriptors. Windows has Win32 APIs such as `CreateFileW`, `ReadFile`, `WriteFile`, and handles.

Zig helps you write portable code through the standard library, but it also lets you call platform-specific APIs when you need them.

#### Prefer `std` for Portable Code

Most ordinary programs should start with Zig’s standard library.

For example, instead of manually calling Windows APIs to read a file, use `std.fs`:

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

pub fn main() !void {
    const data = try std.fs.cwd().readFileAlloc(
        std.heap.page_allocator,
        "hello.txt",
        1024 * 1024,
    );
    defer std.heap.page_allocator.free(data);

    std.debug.print("{s}\n", .{data});
}
```

This code uses `std.fs.cwd()` to access the current working directory. It can work on Windows, Linux, and macOS because `std.fs` hides many platform differences.

That does not mean the differences disappear. It means Zig gives you a common interface for common tasks.

Use the standard library first. Drop down to Windows-specific APIs only when you need behavior that `std` does not provide.

#### Path Handling on Windows

A common beginner mistake is treating paths as plain strings and assuming every operating system uses `/`.

Windows commonly uses backslashes:

```text
C:\Users\alice\Documents\note.txt
```

But in Zig strings, backslash starts an escape sequence. So this is wrong:

```zig
const path = "C:\Users\alice\Documents\note.txt";
```

Zig may interpret parts like `\U`, `\a`, or `\n` as escapes.

You can escape the backslashes:

```zig
const path = "C:\\Users\\alice\\Documents\\note.txt";
```

Or use a raw multiline string style when appropriate:

```zig
const path =
    \\C:\Users\alice\Documents\note.txt
;
```

For portable code, avoid hardcoding platform paths when possible. Use `std.fs.path` helpers and let the standard library handle path rules.

Example:

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

pub fn main() void {
    const joined = std.fs.path.join(
        std.heap.page_allocator,
        &.{ "data", "users", "alice.txt" },
    ) catch return;
    defer std.heap.page_allocator.free(joined);

    std.debug.print("{s}\n", .{joined});
}
```

On Windows, the resulting path follows Windows conventions. On Unix-like systems, it follows Unix conventions.

#### Windows Uses UTF-16 Internally

Windows has an important Unicode detail: many Windows APIs use UTF-16 strings.

Zig source code strings are usually UTF-8 byte sequences. This is normal:

```zig
const name = "hello.txt";
```

But some low-level Windows APIs expect wide strings, usually represented as UTF-16.

If you stay inside Zig’s standard library, you usually do not need to worry about this. `std.fs` and related APIs handle many of these details.

But if you call Win32 APIs directly, string encoding matters. You may need to convert UTF-8 to UTF-16 before calling a Windows API.

This is one reason beginners should avoid calling raw Windows APIs too early. Learn the standard library first, then learn the Windows layer when you need exact operating system behavior.

#### Calling Windows APIs

Zig can call Windows APIs directly.

For example, Zig exposes Windows definitions through the standard library:

```zig
const std = @import("std");
const windows = std.os.windows;
```

From there, you can use Windows types and functions.

A simplified example might look like this:

```zig
const std = @import("std");
const windows = std.os.windows;

pub fn main() void {
    _ = windows;
    std.debug.print("Windows APIs are available through std.os.windows\n", .{});
}
```

For real Windows API calls, you often need to understand handles, error codes, UTF-16 strings, and calling conventions.

Windows programming is a subject of its own. Zig gives you access to it, but it does not remove the need to understand the Windows API model.

#### Building a Windows Executable

On Windows, the simplest command is:

```bash
zig build-exe main.zig
```

This builds for the current system.

You can also explicitly choose a Windows target:

```bash
zig build-exe main.zig -target x86_64-windows
```

This means: build a Windows executable for 64-bit x86 machines.

You may also see targets such as:

```text
aarch64-windows
x86-windows
x86_64-windows
```

The target describes the CPU architecture and operating system.

This is one of Zig’s strengths. The compiler has built-in cross-compilation support, so building for another platform is a normal workflow.

#### Cross-Compiling to Windows

You can build a Windows program from Linux or macOS:

```bash
zig build-exe main.zig -target x86_64-windows
```

This produces a Windows `.exe` file even though you are not running Windows.

That is useful for release builds, CI systems, and open source projects.

For example, a project may produce binaries like:

```text
mytool-linux-x86_64
mytool-macos-aarch64
mytool-windows-x86_64.exe
```

The same Zig source code can be compiled for all of them, as long as the code does not depend on platform-specific APIs without checks.

#### Detecting Windows at Compile Time

Sometimes your program needs different code on Windows.

Zig lets you check the target operating system at compile time:

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

pub fn main() void {
    if (builtin.os.tag == .windows) {
        std.debug.print("Running on Windows\n", .{});
    } else {
        std.debug.print("Running on another operating system\n", .{});
    }
}
```

This check happens using compile-time target information.

You can use this pattern to write platform-specific code:

```zig
if (builtin.os.tag == .windows) {
    // Windows implementation
} else {
    // Unix-like implementation
}
```

This is useful when the operating systems truly require different behavior.

Do not overuse it. If `std` already gives you a portable API, use that instead.

#### File Paths and Command-Line Arguments

Windows command-line behavior has its own edge cases. Quoting rules, Unicode behavior, and shell behavior can differ between PowerShell, Command Prompt, Git Bash, and MSYS2.

For ordinary command-line arguments, use Zig’s standard library:

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

pub fn main() !void {
    var args = try std.process.argsWithAllocator(std.heap.page_allocator);
    defer args.deinit();

    while (args.next()) |arg| {
        std.debug.print("arg: {s}\n", .{arg});
    }
}
```

Run it:

```powershell
.\main.exe hello "two words"
```

The program receives the arguments after shell parsing.

Beginners should remember this distinction:

The shell parses the command line first.

Your Zig program receives the parsed arguments.

Different shells may parse quotes and escapes differently.

#### Console Output

For simple programs, this works:

```zig
std.debug.print("Hello\n", .{});
```

For many tools, that is enough while learning.

But production command-line programs often distinguish between standard output and standard error.

You can write to stdout like this:

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

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();
    try stdout.print("normal output\n", .{});
}
```

And stderr like this:

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

pub fn main() !void {
    const stderr = std.io.getStdErr().writer();
    try stderr.print("error output\n", .{});
}
```

This matters for command-line tools. Users may pipe stdout into another program while still seeing error messages separately.

#### Windows GUI vs Console Programs

A Windows program can be a console program or a GUI program.

A console program opens or uses a terminal. Most beginner Zig examples are console programs.

A GUI program uses the Windows graphical subsystem. It usually has a different entry style and links differently.

For this book, start with console programs. They are easier to build, run, test, and debug.

Once you understand Zig basics, you can use Windows APIs or external libraries to build GUI applications.

#### Linking Libraries on Windows

Windows libraries often appear as `.lib` files and `.dll` files.

A `.dll` is a dynamic library loaded at runtime.

A `.lib` file may be an import library used at link time.

If your Zig program calls a Windows system library or a third-party C library, you may need to link it.

With `build.zig`, linking is usually expressed in the build script rather than typed manually every time.

A simplified build script may include logic such as:

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

exe.linkSystemLibrary("user32");
```

The exact build API can change between Zig versions, so treat build scripts as version-specific code. The important idea is stable: Windows programs often need explicit library linking when they call system or third-party APIs.

#### Common Windows Problems

One common problem is running the wrong executable path.

In PowerShell, this usually does not work:

```powershell
main.exe
```

Use:

```powershell
.\main.exe
```

The `.\` means “run the program from the current directory.”

Another common problem is path escaping:

```zig
const bad = "C:\new\test.txt";
```

This contains escape sequences. Use:

```zig
const good = "C:\\new\\test.txt";
```

Another common problem is assuming Unix APIs exist:

```zig
// Do not assume every POSIX API exists on Windows.
```

Windows is not POSIX. Some APIs exist only on Unix-like systems. Use `std` when possible, and use compile-time OS checks when needed.

#### A Good Beginner Rule

When writing Zig programs for Windows, follow this order:

Use `std` first.

Use `builtin.os.tag` when behavior must differ by operating system.

Use `std.os.windows` when you need Windows-specific APIs.

Use raw C or Win32 interop only after you understand the data types, string encoding, error model, and linking requirements.

That order keeps your code simpler.

#### Complete Example

Here is a small Windows-friendly program that reads command-line arguments and prints them:

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

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    if (builtin.os.tag == .windows) {
        try stdout.print("Target OS: Windows\n", .{});
    } else {
        try stdout.print("Target OS: not Windows\n", .{});
    }

    var args = try std.process.argsWithAllocator(std.heap.page_allocator);
    defer args.deinit();

    var index: usize = 0;
    while (args.next()) |arg| {
        try stdout.print("arg[{d}] = {s}\n", .{ index, arg });
        index += 1;
    }
}
```

Build it for Windows:

```bash
zig build-exe main.zig -target x86_64-windows
```

Run it on Windows:

```powershell
.\main.exe hello "from windows"
```

Possible output:

```text
Target OS: Windows
arg[0] = .\main.exe
arg[1] = hello
arg[2] = from windows
```

This small example shows three important ideas: Zig can target Windows directly, the standard library handles normal command-line work, and platform checks are available when you need them.

Windows support in Zig is practical because Zig combines portable APIs with direct operating system access. Start portable, then become platform-specific only where the program requires it.

