# Appendix J. Migrating Between Zig Versions

## Appendix J. Migrating Between Zig Versions

Zig is still before 1.0, so the language and standard library can change between releases. Code written for one Zig version may not compile on another version without edits.

This appendix explains how to migrate carefully.

### J.1 Know Your Current Version

Check your Zig version first.

```bash
zig version
```

Example output:

```text
0.16.0
```

Do not guess. Migration starts with the exact source version and target version.

### J.2 Pin the Version Per Project

A project should say which Zig version it expects.

Common places:

| Place | Example |
|---|---|
| README | “Requires Zig 0.16.0” |
| CI config | Install exact Zig version |
| Build scripts | Check `zig version` |
| Developer docs | Document setup command |

This prevents one developer from building with 0.15 while another builds with 0.16.

### J.3 Read the Release Notes

Before editing code, read the release notes for every version you cross.

Look for:

| Area | Why |
|---|---|
| Language changes | Syntax or semantic changes |
| Standard library changes | Renamed, moved, or removed APIs |
| Build system changes | `build.zig` changes |
| Compiler changes | New errors or stricter checks |
| Package manager changes | Dependency workflow changes |

Do not migrate by random trial and error only. Compiler errors help, but release notes tell you the design direction.

### J.4 Start with a Clean Build

Before changing anything, confirm the old version builds.

```bash
zig build
zig test .
```

If the old version already fails, migration becomes harder. Fix existing breakage first, or at least record it clearly.

### J.5 Create a Migration Branch

Use version control.

```bash
git checkout -b migrate-zig-0-16
```

Make small commits:

```bash
git add .
git commit -m "Update build.zig for Zig 0.16"
```

Small commits let you isolate problems.

### J.6 Upgrade One Layer at a Time

Do not rewrite the whole project at once.

A good order:

| Step | Work |
|---|---|
| 1 | Build system |
| 2 | Dependency declarations |
| 3 | Imports |
| 4 | Standard library API changes |
| 5 | Language errors |
| 6 | Tests |
| 7 | Cleanup and style |

This avoids mixing unrelated problems.

### J.7 Fix `build.zig` First

If the build file fails, the rest of the project may not compile at all.

Run:

```bash
zig build
```

Fix build errors before source errors.

Common build migration areas:

| Area | Typical change |
|---|---|
| Target options | API shape changes |
| Optimize options | API shape changes |
| Module creation | New module APIs |
| Dependency use | Package manager changes |
| Install steps | Build graph changes |

### J.8 Expect Standard Library Renames

Zig’s standard library changes often.

A function may be:

| Change type | Example |
|---|---|
| Renamed | old name replaced by clearer name |
| Moved | API moved to another namespace |
| Removed | API no longer belongs in `std` |
| Reworked | same task, new design |
| Split | one broad API becomes several smaller APIs |

When a standard library call fails, inspect the new function signature.

### J.9 Search Before Editing

Use search tools.

```bash
rg "std\.ArrayList"
rg "readFileAlloc"
rg "@Type"
rg "Thread\.Pool"
```

Batch similar changes together.

Example:

```bash
rg "readToEndAlloc"
```

Then update all file-reading code in one pass.

### J.10 Let the Compiler Guide You

After each small group of edits, compile again.

```bash
zig build
```

Fix the first meaningful error.

Do not chase every error at once. One wrong type can produce many follow-up errors.

### J.11 Watch for Type Inference Changes

Newer Zig versions may infer slightly different types or require more explicit casts.

Example pattern:

```zig
const n = 10;
```

If the compiler needs a specific type, write it:

```zig
const n: usize = 10;
```

Explicit types are useful at API boundaries, indexes, protocol fields, and binary formats.

### J.12 Replace Removed Builtins

Some builtins change between versions.

For Zig 0.16, `@Type` was replaced by more specific type-construction builtins.

Old style:

```zig
const T = @Type(info);
```

Newer style uses more specific construction APIs.

The exact replacement depends on what type you are building: integer, struct, enum, union, pointer, array, or another kind of type.

### J.13 Review I/O Code Carefully

I/O is one of the areas that changed significantly in Zig 0.16.

File reading, file writing, standard input, standard output, process arguments, and environment handling may need updates.

Do not just patch names. Check the new ownership and I/O model.

Ask:

| Question | Why |
|---|---|
| Does this function now require `std.Io`? | I/O dependency is explicit |
| Does this function allocate? | Allocator must be passed |
| Who owns returned memory? | Cleanup may change |
| Is there a limit parameter? | Avoid accidental unbounded reads |

### J.14 Review Allocator Use

Allocator APIs and container patterns may change.

Check:

| Pattern | What to inspect |
|---|---|
| `ArrayList` | init/deinit style |
| Hash maps | managed vs unmanaged variants |
| Arena allocator | thread-safety and lifecycle |
| Custom allocators | interface changes |
| Test allocators | leak detection behavior |

If a container no longer stores an allocator, pass the allocator to operations that need memory.

### J.15 Review Error Handling

New versions may make errors more precise.

A function that previously returned one error set may now return another.

This can affect code like:

```zig
fn load() MyError!void {
    try std.fs.cwd().openFile("data.txt", .{});
}
```

If the filesystem function returns errors not in `MyError`, Zig will complain.

Fix by:

| Fix | When |
|---|---|
| Widening the error set | Public API can expose more errors |
| Mapping errors manually | Public API should stay stable |
| Handling errors locally | Error can be resolved here |

### J.16 Keep Public APIs Stable When Possible

Internal code can change freely. Public APIs need more care.

If you maintain a library, avoid forcing every user to understand your migration.

Example wrapper:

```zig
pub fn readConfig(allocator: std.mem.Allocator, path: []const u8) !Config {
    // internal Zig-version-specific logic here
}
```

Users call `readConfig`, not every low-level API directly.

### J.17 Update Tests Before Refactoring

After the code compiles, run tests.

```bash
zig build test
```

or:

```bash
zig test src/main.zig
```

Tests reveal semantic changes that compilation alone may miss.

Only refactor after tests are green or after failures are understood.

### J.18 Use Compatibility Shims Sparingly

Sometimes you can hide version differences behind a small helper.

```zig
fn readWholeFile(...) ![]u8 {
    // version-specific implementation
}
```

This is useful if you support multiple Zig versions.

But too many compatibility shims make code harder to read. For applications, it is often better to support one exact Zig version.

### J.19 Do Not Support Too Many Zig Versions

Before Zig 1.0, supporting many versions can be expensive.

For libraries, support a small range.

For applications, pin one version.

Practical choices:

| Project type | Recommendation |
|---|---|
| Personal project | Pin one Zig version |
| Production app | Pin one tested version |
| Library | Support current stable, maybe one previous |
| Teaching material | Use one version consistently |

### J.20 Update CI

Your continuous integration should install the same Zig version you support.

CI should run:

```bash
zig version
zig fmt --check .
zig build
zig build test
```

This catches version drift early.

### J.21 Run Formatter

After migration, run:

```bash
zig fmt .
```

Formatting changes may happen between versions. Let the formatter normalize the code.

### J.22 Recheck Examples and Documentation

Examples often break during migration.

Check:

| File | Why |
|---|---|
| README examples | First thing users copy |
| Tutorial snippets | Often compile-sensitive |
| API docs | May mention old names |
| Build commands | Flags may change |
| Comments | May describe old behavior |

Documentation is part of migration.

### J.23 Recheck Benchmarks

A version migration can change performance.

Run benchmarks again.

Use a release mode:

```bash
zig build -Doptimize=ReleaseFast
```

Compare before and after.

Do not assume a successful compile means identical performance.

### J.24 Recheck Binary Size

Compiler and linker changes can affect binary size.

Measure again after migration.

```bash
ls -lh zig-out/bin/myapp
```

For size-sensitive work, test `ReleaseSmall`.

```bash
zig build -Doptimize=ReleaseSmall
```

### J.25 Recheck Cross Compilation

If your project supports multiple targets, build them.

```bash
zig build -Dtarget=x86_64-linux
zig build -Dtarget=aarch64-linux
zig build -Dtarget=x86_64-windows
zig build -Dtarget=aarch64-macos
```

Migration bugs may appear only on some targets because ABI, libc, filesystem, and linker behavior differ.

### J.26 Migration Checklist

Use this checklist:

| Step | Done |
|---|---|
| Check old Zig version |  |
| Check target Zig version |  |
| Read release notes |  |
| Create migration branch |  |
| Fix `build.zig` |  |
| Fix dependency declarations |  |
| Fix standard library API calls |  |
| Fix language-level errors |  |
| Run formatter |  |
| Run tests |  |
| Run benchmarks if needed |  |
| Test cross targets if needed |  |
| Update README and docs |  |
| Pin version in CI |  |

### J.27 Practical Rule

Migrate Zig projects with small, boring steps.

Change one category at a time.

Compile often.

Read signatures.

Keep allocation, errors, and ownership visible.

Do not treat migration as only a search-and-replace task. Zig version changes often reflect design changes, especially before 1.0.

