# snapdir for Zig Idiomatic Zig bindings for snapdir. The binding is a thin `@cImport` wrapper over the snapdir **C ABI** (`snapdir.h`), which itself wraps the canonical `snapdir-api` core — so the manifests and snapshot IDs it produces are **bit-identical** to the CLI and to every other binding. Snapshot IDs are 64-character lowercase hex. ## Install The Zig binding is **build-from-source** (there is no prebuilt native blob in a Zig registry). Build the C ABI from the [`snapdir-ffi`](https://crates.io/crates/snapdir-ffi) crate on crates.io, then `zig fetch --save` the wrapper (Zig 0.13.0): ```sh # needs a Rust toolchain + cbindgen; on Alpine also: apk add libgcc cargo build --release # -> libsnapdir_ffi.a cbindgen --config cbindgen.toml --crate snapdir-ffi --output include/snapdir.h . zig fetch --save git+https://github.com/snapdir/snapdir# ``` See [Install snapdir for Zig](/install/zig/) for the `build.zig` wiring and the Alpine/musl `libgcc_s` note. ## Usage Every allocating function takes a `std.mem.Allocator` first (use an arena or `std.testing.allocator` for leak detection). The `id`/`manifest` calls are synchronous; `push`/`pull`/`fetch`/`diff` do the transfer. The store URI is always an argument — `file://$PWD/store` for a local store, `gs://bucket/prefix` for Google Cloud: ```zig const std = @import("std"); const snapdir = @import("snapdir"); pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const a = gpa.allocator(); // id → 64-char snapshot id ([64]u8 value, no free). const id = try snapdir.id(a, "./my-dir", .{}); std.debug.print("{s}\n", .{id}); // Full manifest (raw text + parsed entries); free with deinit. var m = try snapdir.manifest(a, "./my-dir", .{}); defer m.deinit(a); for (m.entries) |e| { // e.type (PathType), e.perm (u32), e.checksum ([64]u8), e.size (u64), e.path } // snapdir.idFromManifest(m) == id // Options: exclude a regex, do not follow symlinks. const filtered = try snapdir.id(a, "./my-dir", .{ .no_follow = true, .exclude = "\\.tmp$" }); _ = filtered; // Transfer to a store (URI is an argument). const pushed = try snapdir.push(a, "./my-dir", "file:///…/store", .{}); try snapdir.pull(a, &pushed, "file:///…/store", "./restored", .{}); // fetch caches objects locally; diff compares two store contents // as STATUSPATH entries (A / D / M / =), byte-identical to the CLI. const changes = try snapdir.diff(a, "file:///…/store-a", "file:///…/store-b", .{}); defer { for (changes) |c| a.free(c.path); a.free(changes); } for (changes) |c| std.debug.print("{c}\t{s}\n", .{ @intFromEnum(c.status), c.path }); } ``` To pin a specific snapshot when diffing, use the `store@id` convention (the last `@` splits the store URI from the 64-hex ID), pull each side into a temp directory, and diff those — see the canonical `examples/zig/app.zig`. ## Errors Errors are a Zig `error{...}` set mapped from the 8 stable C ABI codes, plus `OutOfMemory`. `catch |err| switch (err)` handles them exhaustively: ```zig const id = snapdir.id(a, "/no/such/path", .{}) catch |err| switch (err) { error.IoError => return, // IO_ERROR — filesystem / network failure error.HashMismatch => return, // HASH_MISMATCH — content did not verify error.StoreError => return, // STORE_ERROR — object store rejected the op error.InFlux => return, // IN_FLUX — tree changed mid-snapshot error.CatalogError => return, // CATALOG_ERROR — manifest/catalog problem error.InvalidId => return, // INVALID_ID — malformed snapshot id error.InvalidStore => return, // INVALID_STORE — unparseable store URI error.Conflict => return, // CONFLICT — concurrent write conflict else => return err, // OutOfMemory }; ``` The same eight codes (`IO_ERROR`, `HASH_MISMATCH`, `STORE_ERROR`, `IN_FLUX`, `CATALOG_ERROR`, `INVALID_ID`, `INVALID_STORE`, `CONFLICT`) appear in every binding, so error handling ports cleanly across languages. ## Platform support Every binding runs on Linux (glibc and Alpine/musl, x64 and arm64) and macOS; Windows is unsupported. Each release is CI-verified from its live public registry. | Platform | Supported | | --- | --- | | Linux glibc (x64 + arm64) | yes | | Linux musl / Alpine (x64 + arm64) | yes | | macOS | yes | | Windows | no | The one binding-wide exception is **Java**, which is glibc-only today (musl support is planned for 1.11.1). The Zig binding links on glibc and musl alike.