# macOS Support

### macOS Support

macOS is one of Zig’s main desktop targets. You can use Zig on macOS to write command-line tools, development utilities, servers, libraries, and cross-platform applications.

For beginners, the main idea is simple: macOS is Unix-like, so many habits from Linux also apply. It uses `/` paths, terminals, permissions, stdin, stdout, stderr, pipes, and exit codes. But macOS also has its own system libraries, application model, security rules, and CPU architecture details.

A simple Zig program works normally on macOS:

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

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

Build it:

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

Run it:

```bash
./main
```

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

#### macOS Is Unix-Like

macOS has a Unix-style command line. Paths look like this:

```text
/Users/alice/projects/app/main.zig
```

The root directory is:

```text
/
```

The user’s home directory is usually:

```text
/Users/alice
```

So file code that works on Linux often looks similar on macOS:

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

pub fn main() !void {
    const file = try std.fs.cwd().openFile("hello.txt", .{});
    defer file.close();

    var buffer: [256]u8 = undefined;
    const n = try file.read(&buffer);

    std.debug.print("{s}\n", .{buffer[0..n]});
}
```

This opens `hello.txt` in the current working directory, reads some bytes, and prints them.

Use `std.fs` first. It gives you portable file handling across macOS, Linux, and Windows.

#### Shells on macOS

The default shell on modern macOS is usually `zsh`.

You will commonly run commands like:

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

You can also use Bash, Fish, or another shell, but shell syntax can vary.

For Zig programs, this matters mostly when reading command-line arguments. The shell parses quotes before your program receives the arguments.

Example:

```bash
./main hello "two words"
```

Your Zig program receives three arguments:

```text
./main
hello
two words
```

A simple argument-printing program:

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

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

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

Build and run:

```bash
zig build-exe main.zig
./main hello "two words"
```

Possible output:

```text
arg[0] = ./main
arg[1] = hello
arg[2] = two words
```

#### macOS CPU Targets

Modern macOS machines commonly use Apple Silicon CPUs, such as M1, M2, M3, and later. These use the `aarch64` architecture.

Older Intel Macs use `x86_64`.

That gives two common Zig targets:

```text
aarch64-macos
x86_64-macos
```

On an Apple Silicon Mac, a normal local build usually targets `aarch64-macos`:

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

To build explicitly for Apple Silicon:

```bash
zig build-exe main.zig -target aarch64-macos
```

To build for Intel Macs:

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

This is useful when distributing binaries. Some users may still have Intel Macs, while newer machines use Apple Silicon.

#### Universal Binaries

A macOS universal binary contains code for more than one CPU architecture, usually `x86_64` and `aarch64`.

Conceptually, it is one file containing two builds:

```text
mytool
  x86_64 code
  aarch64 code
```

Zig can build each architecture separately. Then macOS tools such as `lipo` can combine them.

Example idea:

```bash
zig build-exe main.zig -target x86_64-macos -femit-bin=mytool-x86_64
zig build-exe main.zig -target aarch64-macos -femit-bin=mytool-aarch64
lipo -create -output mytool mytool-x86_64 mytool-aarch64
```

Now `mytool` can run natively on both Intel and Apple Silicon Macs.

For beginner projects, you do not need universal binaries immediately. But for distributing public macOS tools, they are useful.

#### Detecting macOS at Compile Time

Zig lets you check the target operating system:

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

pub fn main() void {
    if (builtin.os.tag == .macos) {
        std.debug.print("Target OS: macOS\n", .{});
    } else {
        std.debug.print("Target OS: not macOS\n", .{});
    }
}
```

This is a compile-time target check.

Use it when behavior must differ by platform:

```zig
if (builtin.os.tag == .macos) {
    // macOS-specific implementation
} else {
    // other implementation
}
```

But prefer portable standard library APIs when they are enough.

#### Standard Output and Standard Error

Like Linux, macOS programs use:

`stdin` for input.

`stdout` for normal output.

`stderr` for error output.

Write to stdout:

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

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

Write to stderr:

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

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

This matters because shell users redirect streams:

```bash
./tool > output.txt
./tool 2> errors.txt
```

Well-behaved command-line tools keep normal output and error output separate.

#### Permissions

macOS uses Unix-style permissions.

A file may need execute permission before it can run:

```bash
chmod +x mytool
./mytool
```

You may also run into macOS security checks when downloading binaries from the internet. macOS may block unsigned or quarantined executables.

For local learning, this usually does not matter. For distributing software, you may need to understand signing, notarization, Gatekeeper, and quarantine attributes.

Those are macOS deployment topics, not core Zig topics. But they affect real applications.

#### Frameworks and System Libraries

macOS has system frameworks such as:

```text
Foundation
CoreFoundation
AppKit
Security
CoreGraphics
Metal
```

A framework is a bundle of code and resources provided by the operating system.

Command-line Zig programs often do not need these. But GUI programs, graphics programs, system tools, and native macOS integrations may need them.

Zig can link system libraries and frameworks through the build system.

A simplified build idea:

```zig
exe.linkFramework("Foundation");
```

For larger macOS programs, you will often combine Zig with C APIs, Objective-C APIs, or system frameworks.

#### Objective-C and macOS APIs

Many native macOS APIs are written for Objective-C or Swift.

Zig can interoperate with C directly. Objective-C interop is more complex because Objective-C has its own runtime and message-passing model.

For beginners, do not start with AppKit or Cocoa. Start with command-line programs. Then learn C interop. After that, approach Objective-C runtime calls or wrapper libraries if you need native macOS GUI work.

A practical order is:

Learn Zig basics.

Learn Zig’s standard library.

Learn C interop.

Learn macOS frameworks.

Learn app packaging, signing, and notarization.

That order avoids mixing too many hard topics at once.

#### Dynamic Libraries on macOS

macOS uses dynamic libraries with names like:

```text
libsomething.dylib
```

Applications may also link against frameworks.

When distributing a macOS binary, you must consider where its dynamic libraries come from. A program that runs on your machine may fail on another machine if it depends on a library that is not installed there.

This is similar to Linux dynamic linking, but macOS has its own tooling and rules.

For small command-line tools, prefer minimal dependencies when possible.

#### Cross-Compiling to macOS

Zig supports targeting macOS, but macOS builds can involve Apple SDK details, system frameworks, and platform rules.

A simple target command looks like:

```bash
zig build-exe main.zig -target aarch64-macos
```

or:

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

For plain Zig code with no special platform dependencies, this is straightforward.

For programs that link macOS frameworks or system libraries, cross-compilation can require the correct SDK and build configuration.

The beginner rule is simple: cross-compiling basic Zig code is easy; cross-compiling native macOS apps requires more platform knowledge.

#### Homebrew and Zig

Many macOS users install developer tools through Homebrew.

You may see commands like:

```bash
brew install zig
```

But for learning a specific Zig version, downloading from Zig’s official site is often clearer. Package managers may lag behind or may install a version different from the one used in this book.

Always check:

```bash
zig version
```

That tells you which Zig compiler you are actually using.

#### Complete Example

Here is a small macOS-friendly command-line program:

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

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

    if (builtin.os.tag == .macos) {
        try stdout.print("Target OS: macOS\n", .{});
    } else {
        try stderr.print("warning: this program was built for another OS target\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:

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

Run it:

```bash
./main hello "from macOS"
```

Possible output:

```text
Target OS: macOS
arg[0] = ./main
arg[1] = hello
arg[2] = from macOS
```

Build explicitly for Apple Silicon:

```bash
zig build-exe main.zig -target aarch64-macos
```

Build explicitly for Intel macOS:

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

#### The Practical View

For ordinary command-line programs, macOS support feels close to Linux support. You use Unix-style paths, terminal commands, permissions, pipes, stdout, stderr, and exit codes.

For native macOS applications, the platform becomes more specific. You need to understand frameworks, Objective-C or Swift APIs, app bundles, signing, notarization, and Apple’s deployment rules.

Start with the portable layer. Use `std.fs`, `std.process`, stdin, stdout, stderr, and compile-time target checks. Then learn macOS-specific APIs only when your program needs them.

