cargo-zigbuild: cross-compiling Rust with zig cc as the linker
Compile Cargo project with zig as linker
At a glance
- What is it?
- cargo-zigbuild wraps cargo build and routes linking through zig cc so a Rust project can target aarch64-unknown-linux-gnu or x86_64-apple-darwin without a hand-built toolchain. It works best when you accept Zig's sysroot rules and the glibc pinning caveats.
- Who is it for?
- Adopt cargo-zigbuild if you ship Rust binaries for several Linux architectures or for macOS from a Linux CI runner and you are willing to pin a glibc version with a target suffix such as aarch64-unknown-linux-gnu.2.17. Do not adopt it if you need a fully static glibc binary, since -C target-feature=+crt-static is not supported and the README points to *-musl targets instead, or if your build depends on system headers under /usr/include without extra CFLAGS.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The cross-compilation toolchain problem cargo-zigbuild removes
Cross-compiling Rust is mostly a linker problem, not a compiler problem. rustc can emit object code for aarch64-unknown-linux-gnu on an x86_64 host, but something still has to produce the final binary and resolve libc symbols for the target platform. The conventional answer is a full cross toolchain: a target gcc, a sysroot, and often a container image per architecture. cargo-zigbuild takes a different route. It invokes cargo build but substitutes zig cc as the linker, because Zig ships a bundled libc and can act as a drop-in replacement for gcc and clang. The README describes the project as compiling a Cargo project "with zig as linker for easier cross compiling". The audience is anyone who already builds Rust in CI and finds per-target toolchain images expensive to maintain. If you only ever build for the host, the tool adds a dependency and a set of Zig-specific failure modes for no benefit, and the README states that without a --target the command effectively runs a regular cargo build.
What cargo zigbuild actually does at build time
The repository layout shows a small Rust binary under src/ with a Cargo.toml whose dependencies tell you the shape of the work: cargo_metadata, cargo-options, cargo-config2, target-lexicon, rustc_version, and rustflags. That is the profile of a cargo subcommand that reads the manifest and the resolved target, then rewrites the linker configuration before delegating to cargo. The crate also depends on goblin and scroll, which parse Mach-O and other binary formats, and on fat-macho behind the default universal2 feature, which is how a single macOS output can carry more than one architecture. Rather than replacing cargo, cargo zigbuild sets environment variables and flags so the compiler driver ends up being zig cc for the target. It exports CARGO_ZIGBUILD_TARGET, described in the README as the resolved zig target triple passed to build scripts, for example x86_64-linux-gnu.2.36. The suffixed form CARGO_ZIGBUILD_TARGET_<target> is always set; the unsuffixed name only appears when a single --target is given. Build scripts that need the target triple should read the suffixed variable, because the plain one may be absent in multi-target invocations.
Installing cargo-zigbuild and running a first cross build
The README gives two installation paths. The Cargo route installs the subcommand from crates.io with the lockfile enforced.
cargo install --locked cargo-zigbuildThe pip route additionally pulls in the ziglang package, so you do not install Zig separately.
pip install cargo-zigbuildZig itself must be on PATH unless you use the pip package, which the README says installs ziglang automatically. The three-step usage section is: install Zig, add the Rust target with rustup, then run the subcommand. For an aarch64 Linux build from an x86_64 host that looks like this.
rustup target add aarch64-unknown-linux-gnu
cargo zigbuild --target aarch64-unknown-linux-gnuWhat you should see is a normal cargo build log ending in a binary under target/aarch64-unknown-linux-gnu/. If Zig or the target is missing, the command fails before linking rather than producing a broken artifact. To pin a minimum glibc, append the version to the target string, which the README shows as a suffix on the triple.
cargo zigbuild --target aarch64-unknown-linux-gnu.2.17There are also published Docker images, including one with the macOS SDK pre-installed, invoked as ghcr.io/rust-cross/cargo-zigbuild with the project mounted at /io.
docker run --rm -it -v $(pwd):/io -w /io ghcr.io/rust-cross/cargo-zigbuild \
cargo zigbuild --release --target x86_64-apple-darwinThe glibc suffix is convenient and explicitly caveated
Appending .2.17 to a gnu target is the feature most people come for, and the README is unusually candid about its limits. Zig's default glibc version varies by Zig release; the README cites v12 to v14 releases defaulting to glibc 2.28. Four caveats are listed. First, without a --target, Zig is not used at all and the command is a plain cargo build, so a CI job that conditionally drops the flag silently changes what it is testing. Second, an invalid glibc version produces no relayed warning from zig cc about the fallback it chose, so a typo in the suffix fails quietly. Third, the version check is not equivalent to dynamic linking against a specific glibc on the build host: version 2.32 can be specified and will run on a host with only 2.31 when it should abort, while 2.33 is correctly detected as incompatible on that same host. Fourth, certain RUSTFLAGS opt out of Zig entirely, and -L path/to/files causes Zig to ignore -C target-feature=+crt-static. Static linking of glibc through that flag is not supported, because upstream zig cc lacks it. If you need a fully static binary, the README directs you to a *-musl target instead. That is a real boundary, not a configuration detail.
Missing headers and libraries under zig cc
The most common surprise is a project that builds with cargo build and fails with cargo zigbuild. The README explains why: cargo zigbuild always passes -nostdinc to zig cc, which excludes standard header locations such as /usr/include, and a configured --target additionally opts Zig out of standard system search paths. Two error shapes are quoted. A missing header appears as fatal error: 'libelf.h' file not found, and a missing shared library as error: unable to find dynamic system library 'elf' using strategy 'no_fallback'. The README suggests prepending CFLAGS='-isystem /usr/include' or RUSTFLAGS='-L /usr/lib64' as starting points, but warns against CPATH=/usr/include, which mixes the host glibc headers with the ones Zig provides and produces macro redefinition warnings such as '__GLIBC_MINOR__' macro redefined. The distinction matters: -isystem is a search path hint, while CPATH injects headers into every translation unit. If your build depends heavily on system packages that installed headers into /usr/include, expect to spend time on include paths before the first successful link.
When to reach for cross or xwin instead
cargo-zigbuild is one option among several in the Rust cross-compilation space, and the alternatives differ in approach rather than degree. cargo cross runs the build inside a container image per target, so the toolchain, sysroot and system libraries come from the image instead of from Zig's bundled libc. That is heavier and slower to start, but it gives you a real target sysroot with the distribution's headers and libraries already present, which is exactly the situation where cargo zigbuild needs CFLAGS and RUSTFLAGS adjustments. cargo-xwin targets Windows MSVC from non-Windows hosts by downloading the Microsoft CRT and Windows SDK headers; cargo-zigbuild does not cover that case, since it is built around zig cc and the targets Zig supports. The practical split is this: pick cargo-zigbuild when you want a single binary subcommand and no container runtime, pick a container-based tool when your build links against distribution libraries that Zig's sysroot does not ship, and pick xwin when the target is Windows MSVC.
Maintenance, version pinning and the MIT licence
The last push to the repository was on 2026-09-14, and the most recent release listed is v0.23.4 on 2026-09-02, with v0.23.3 and v0.23.2 in the weeks before. The crate is not archived. The Cargo.toml sets rust-version to 1.88 and edition to 2024, so the toolchain you use to install the subcommand has a floor. Upgrade cost sits mostly on the Zig side rather than the Rust side: the README ties default glibc behaviour to Zig release lines, so bumping the Zig version on a build machine can shift the implicit glibc target for every gnu build unless you pin the suffix explicitly. The Dockerfile pins ZIG_VERSION and RUST_VERSION as build arguments, and it notes that Zig 0.14.0 changed the tarball naming convention from zig-{os}-{arch}-{version} to zig-{arch}-{os}-{version}, so any custom image script that constructs download URLs needs the same branch. The project is MIT licensed, which is permissive and imposes no source-disclosure requirement on your own binaries; that is a statement about the licence text, not legal advice, and the bundled Zig toolchain and any macOS SDK you ship alongside your build carry their own terms that you should read separately.
Editorial conclusion
Adopt cargo-zigbuild if you ship Rust binaries for several Linux architectures or for macOS from a Linux CI runner and you are willing to pin a glibc version with a target suffix such as aarch64-unknown-linux-gnu.2.17. Do not adopt it if you need a fully static glibc binary, since -C target-feature=+crt-static is not supported and the README points to *-musl targets instead, or if your build depends on system headers under /usr/include without extra CFLAGS. Before committing, verify that your dependency tree links cleanly under zig cc with the exact target suffix you intend to ship, and check whether any build script reads CARGO_ZIGBUILD_TARGET.
Frequently asked questions
What is cargo zigbuild?
It is a cargo subcommand that compiles a Cargo project using zig as the linker, which the README describes as making cross compiling easier. You install it with cargo install --locked cargo-zigbuild or pip install cargo-zigbuild, add a Rust target with rustup, then run cargo zigbuild --target <triple>.
How do I install cargo-zigbuild?
Two documented routes exist: cargo install --locked cargo-zigbuild from crates.io, or pip install cargo-zigbuild, which also installs the ziglang package automatically. There are also published Docker images that include Rust and, in one case, a pre-installed macOS SDK.
Does cargo zigbuild work without specifying a target?
The README states that if you do not provide a --target, Zig is not used and the command effectively runs a regular cargo build. Passing the target is what activates the zig cc linker path.
Can cargo-zigbuild produce a fully static glibc binary?
No. The README says -C target-feature=+crt-static for statically linking to glibc is not supported because upstream zig cc lacks support, and it directs you to a *-musl target if you need a fully static binary.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/rust-cross-cargo-zigbuild)