Strategies for libraries to support multiple Zig versions?

How do folks handle coding for different Zig versions?

For example, for users on Zig 0.16.0, they might use the std.fmt.allocPrint function. Perfectly fine: Zig Documentation

Buuuut someone else might be running Zig 0.17.0-dev.1509+bb296ab9b. In that version, allocPrint says: Zig Documentation

Deprecated in favor of Allocator.print.

I’m writing a library, and ideally, I’d like to support 0.16.0 and latest main. Is there a way I could say something like this in my library?

if (version == 0.16.0) {
  std.fmt.allocPrint(...);
} else if (version 0.17.x) {
  allocator.print(...);
}

Or how do folks handle this situation?

1 Like

I expect most use source control branches rather than anything inline.

4 Likes

If you aren’t developing a Zig package meant for consumption by others (i.e. you’re compiling an executable), it’s probably easiest to use different branches like @pachde suggests. (But it’s even easier to just pick one target and stick to that.)

If you are developing a package and you only want to maintain one branch that works with multiple Zig versions (e.g. the latest tagged release and master) instead of two that target different Zig versions, there are two main tools you can use:

  1. Use reflection builtins like @hasDecl, @hasField, @FieldType @TypeOf, @typeInfo etc. to detect features added to/removed from/changed in master:
    if (@hasDecl(std.Build.Step.ConfigHeader, "addIdent")) {
        build_config_h.addIdent(name, value);
    } else {
        // TODO: Remove after 0.16.0
        build_config_h.values.put(name, .{ .ident = value }) catch @panic("OOM");
    }
    
  2. Use @import("builtin").zig_version:
    if (@import("builtin").zig_version.major <= 15) { // TODO: Remove after 0.16
        // Fix "duplicate symbol" errors by redefining a problematic weak symbol definition in
        // wchar.h which was introduced in Windows SDK version 10.0.26100.0 and which LLVM 20
        // doesn't understand how to handle.
        sdl_mod.addCMacro("_Avx2WmemEnabledWeakValue", "_Avx2WmemEnabled");
    }
    

Both of these examples are from an older revision of my SDL3 Zig package, which has always supported the latest tagged release and master for as long as it has existed.

Feature detection using reflection I usually find easier to write and maintain because then you won’t need to look up the exact revision of Zig master a particular breaking change was introduced in. But zig_version is useful if there’s some feature within the Zig toolchain itself (as opposed to the std API) that doesn’t work the same in both versions, such as a miscompilation or a change in translate-c.

7 Likes

You mean like keep 2 versions of your project? One version of your project for Zig main on one branch and another branch for another version of Zig? :grimacing: Seems dicey trying to manage different versions like that…

The other answers are good, but I also want to point out that for this particular case, you can just keep using std.fmt.allocPrint. It shouldn’t be deleted until sometime between 0.17.0 and 0.18.0 (and by that time Allocator.print will work on both 0.17.0 and latest main, so switch to it then)

1 Like