# Coming from an Older Zig

> What changed between the last stable releases and the master this guide targets.

This guide targets **Zig master**: the version in the footer is the compiler
that actually compiled and ran every snippet here. If you are on a tagged
release, some of this code will not build for you.

Rather than maintaining a parallel guide per release, this page gives you the
**delta**: what changed, what it looks like now, and which chapter covers it.
That is usually what you actually need.

<aside>

**Why the old code below is not runnable.** Every other snippet on this site has
a Run button because CI compiled and executed it. The "before" examples here
deliberately *cannot* compile on current Zig (that is the whole point), so they
are shown as plain code. The "after" side is verified, in the chapter each one
links to.

</aside>

## At a glance

| What | Before | Now |
| --- | --- | --- |
| `main` signature | `pub fn main() !void` | `pub fn main(init: std.process.Init) !void` |
| Writers | unbuffered, no flush | buffered, **must** `flush` |
| File API | `std.fs.File` | `std.Io.File`, and takes an `Io` |
| Array repeat | `"-" ** 5` | `@splat` (`**` is **gone**) |
| `ArrayList` | `init(allocator)` | `.empty`, allocator per call |
| Mutex | `std.Thread.Mutex` | `std.Io.Mutex`, `lock(io)` |
| Custom `format` | four parameters, used by `{}` | one writer parameter, used by `{f}` |
| Enum reflection | `info.fields[i].name` | `info.field_names[i]` |
| `addExecutable` | source file directly | `root_module` |

---

## `main` takes an argument

The one that breaks the very first program you write.

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

```zig
// now
pub fn main(init: std.process.Init) !void {
    try std.Io.File.stdout().writeStreamingAll(init.io, "Hello\n");
}
```

`init.io` is an **`Io` instance**: the same idea as `Allocator`, applied to
anything that can block. There is no global stdout; you must be handed the
capability to write.

→ [Hello World](https://www.ziglang.in/learn/getting-started/hello-world/)

## Writers are buffered ("writergate", 0.15)

```zig
// before: appeared immediately
const stdout = std.io.getStdOut().writer();
try stdout.print("{d}\n", .{42});
```

```zig
// now: nothing is written until you flush
var buf: [1024]u8 = undefined;
var file_writer = std.Io.File.stdout().writerStreaming(io, &buf);
const out = &file_writer.interface;
try out.print("{d}\n", .{42});
try out.flush();          // ← forget this and you get no output at all
```

The buffer belongs to the *interface*, which is why you supply it: no hidden
allocation. The missing `flush` is the single most common symptom of following
an older tutorial.

→ [Readers and Writers](https://www.ziglang.in/learn/standard-library/readers-and-writers/)

## Filesystem moved onto `Io` (0.16)

```zig
// before
var file = try std.fs.cwd().openFile("data.txt", .{});
defer file.close();
```

```zig
// now
var dir = std.Io.Dir.cwd();
const file = try dir.openFile(io, "data.txt", .{});
defer file.close(io);
```

Uniform rule: anything that touches the disk takes the `io` instance.

→ [Filesystem](https://www.ziglang.in/learn/standard-library/filesystem/)

## `**` no longer exists

```zig
// before
const dashes = "-" ** 5;
```

```zig
// now
const dashes: [5]u8 = @splat('-');
```

Worth calling out because the failure is actively misleading: `**` no longer
tokenises at all, so the compiler reports **"binary operator `*` has whitespace
on one side, but not the other"**, which tells you nothing about what actually
happened. `++` concatenation is unaffected.

→ [Arrays](https://www.ziglang.in/learn/language-basics/arrays/)

## `ArrayList` and hash maps are unmanaged

```zig
// before: the list stored your allocator
var list = std.ArrayList(u8).init(allocator);
defer list.deinit();
try list.append('a');
```

```zig
// now: you pass it to anything that can allocate
var list: std.ArrayList(u8) = .empty;
defer list.deinit(gpa);
try list.append(gpa, 'a');
```

More typing, but the allocator is visible at every call site that can fail. The
same change applies to `StringHashMapUnmanaged` and `AutoHashMapUnmanaged`.

→ [ArrayList](https://www.ziglang.in/learn/standard-library/arraylist/) · [Hash Maps](https://www.ziglang.in/learn/standard-library/hash-maps/)

## Synchronisation moved to `Io`

```zig
// before
var mutex: std.Thread.Mutex = .{};
mutex.lock();
defer mutex.unlock();
```

```zig
// now
var mutex: std.Io.Mutex = .init;
try mutex.lock(io);
defer mutex.unlock(io);
```

`lock` can fail only because it can be *cancelled*. `std.Thread.spawn` still
exists, but structured concurrency via `io.async` and `Io.Group` is the better
default, and works on targets with no threads at all.

→ [Threads](https://www.ziglang.in/learn/standard-library/threads/) · [Concurrency](https://www.ziglang.in/learn/standard-library/concurrency/)

## Custom `format` methods

```zig
// before
pub fn format(
    self: Point,
    comptime fmt: []const u8,
    options: std.fmt.FormatOptions,
    writer: anytype,
) !void { ... }
```

```zig
// now
pub fn format(self: Point, writer: *std.Io.Writer) std.Io.Writer.Error!void {
    try writer.print("({d}, {d})", .{ self.x, self.y });
}
```

And it is **no longer invoked by `{}`**; you must ask for it with `{f}`.
`{t}` prints an enum tag name or error name.

→ [Advanced Formatting](https://www.ziglang.in/learn/standard-library/advanced-formatting/)

## Enum reflection

```zig
// before
const fields = @typeInfo(Direction).@"enum".fields;
fields[2].name;
```

```zig
// now: parallel arrays
const info = @typeInfo(Direction).@"enum";
info.field_names[2];
info.field_values[2];
```

This one is recent: it landed between `0.17.0-dev.644` and `dev.1441`, and was
the only break across all 53 snippets in this guide when the compiler was
updated across roughly 800 commits.

→ [Enums](https://www.ziglang.in/learn/language-basics/enums/)

## `build.zig`

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

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

→ [Zig Build](https://www.ziglang.in/learn/build-system/zig-build/)

---

## If you are staying on a release

Nothing here is a reason to upgrade in a hurry. A tagged release is the right
choice for most work; master is what this guide tracks because a guide that
lags is a guide that quietly lies.

Use this page as a translation layer: read the chapter for the concept, then
map the syntax back. The *ideas* (explicit allocators, errors as values,
exhaustive switches, `defer`, comptime) have not changed at all, and those are
the parts worth your attention.
