在 Windows 上写 Rust,然后编译到 Linux:手动、cross、cargo-zigbuild 到底怎么选

很多人第一次做 Rust 交叉编译时,都会从同一个命令开始:

rustup target add x86_64-unknown-linux-musl

然后信心满满地敲下:

cargo build --release --target x86_64-unknown-linux-musl

有时候它真的能过。

然后你换了一个真实项目,里面有 openssl-sysringsqlite3-syszstd-sys,编译器突然开始报一堆 linker、header、library 找不到的错误。你会怀疑是不是 Rust 的交叉编译不成熟。

其实不是。

问题在于,rustup target add 只安装目标平台的 Rust 标准库。Rust 官方文档也说得很清楚:交叉编译通常还需要额外工具,尤其是 linker。比如编译 Android target,还需要 Android NDK。(Rust语言)

所以 Rust 交叉编译真正要回答的不是一个问题,而是两个问题:

Rust 能不能为目标平台生成代码?

最终能不能把这些代码链接成一个能运行的二进制?

第一个问题通常比较简单。第二个问题才是大坑。

先把选择说清楚

如果你只是想在 Windows 上开发,然后把程序部署到 Linux 服务器,大概有四条路线。

路线适合场景优点麻烦点
手动 cargo build --target纯 Rust 项目、musl target、你愿意自己配 linker最透明,没有额外封装linker、sysroot、native deps 都要自己处理
cross需要稳定复现构建环境,尤其是 Linux GNU/ARM targetDocker/Podman 封装工具链,团队和 CI 里好用依赖容器环境,遇到特殊 C 库时要定制镜像
cargo-zigbuild想少配 linker,又不想上 Docker用 Zig 做 linker,Linux GNU/musl 很方便,还能指定 glibc 版本不是万能,native headers/libs 仍可能要配置
WSL/Linux CI生产构建、依赖复杂、你不想赌最接近目标环境不算“纯 Windows 本地构建”

我个人的排序很简单:

纯 Rust,先试 musl。
依赖复杂,用 WSL 或 Linux CI。
想在本机少折腾 linker,试 cargo-zigbuild
要团队可复现构建和跨架构测试,用 cross

手动路线:最干净,也最容易暴露问题

手动交叉编译的好处是你知道每一步发生了什么。

比如目标是 Linux musl:

rustup target add x86_64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-musl

如果项目足够干净,这就结束了。

但“足够干净”这个条件很重要。没有 native 依赖的 CLI、小服务、agent 工具,通常比较顺。可一旦项目里有 C/C++ 依赖,Rust 之外的世界就进来了。

这时候你需要的可能是:

# .cargo/config.toml

[target.x86_64-unknown-linux-musl]
linker = "x86_64-linux-musl-gcc"

[target.aarch64-unknown-linux-musl]
linker = "aarch64-linux-musl-gcc"

Cargo 本身支持按 target 配置 linker、runner、rustflags 等项,这也是手动交叉编译的标准做法。(Rust语言)

手动路线适合你想把事情弄明白的时候。它也适合比较简单的项目。
但如果你在 Windows 上手搓 x86_64-unknown-linux-gnu,通常会开始痛苦。

因为 GNU Linux target 不是只有 Rust target。它还牵涉 glibc、Linux sysroot、动态链接器路径、目标平台库版本。你可以配出来,但除非你有明确理由,不然我不建议把这件事变成日常工作流。

cross:把目标平台环境放进容器里

cross 的思路和手动路线不一样。

它不要求你在宿主机上装一堆目标平台工具链,而是用 Docker 或 Podman 提供构建环境。它的 README 里直接把自己描述为“zero setup cross compilation and cross testing of Rust crates”,并说明它会提供交叉编译所需的环境、工具链和交叉编译库。(GitHub)

安装方式:

cargo install cross --git https://github.com/cross-rs/cross

使用方式几乎就是把 cargo 换成 cross

cross build --release --target aarch64-unknown-linux-gnu

测试也可以:

cross test --target aarch64-unknown-linux-gnu

cross 的 CLI 和 Cargo 很像,但它依赖 Docker 或 Podman。官方 README 也写明了 Docker/Podman 是依赖项,Docker 需要 20.10 或更高版本,Podman 需要 3.4.0 或更高版本。(GitHub)

这就是它的核心价值:不要让每个开发者都在自己机器上配一套神秘 toolchain。把构建环境放进容器,团队里每个人和 CI 尽量用同一套东西。

它尤其适合这些场景:

你要编 aarch64-unknown-linux-gnu
你要在 CI 里产出多架构 Linux binary。
你想跑 cross test,而不只是 build。
项目有 native 依赖,但能通过 apt 包或自定义镜像解决。

例如项目依赖 OpenSSL,cross 可以在 Cross.toml 里给目标平台装依赖:

[target.aarch64-unknown-linux-gnu]
pre-build = [
  "dpkg --add-architecture $CROSS_DEB_ARCH",
  "apt-get update && apt-get --assume-yes install libssl-dev:$CROSS_DEB_ARCH"
]

cross 的文档说明可以在项目根目录放 Cross.toml,也可以通过 CROSS_CONFIG 指定配置文件;如果默认镜像不够,还可以用 pre-build、自定义 Dockerfile 或指定 image。(GitHub)

不过 cross 也不是银弹。

它依赖容器。Windows 上通常意味着 Docker Desktop 或 WSL2。
它的默认镜像覆盖常见情况,但不可能覆盖所有 C/C++ 库。
如果你的依赖很偏,最后还是要写 Cross.toml 或维护自己的 Dockerfile。

但这不算缺点,更像是代价。你用容器换来了可复现构建。

cargo-zigbuild:用 Zig 把 linker 这件事变轻

cargo-zigbuild 是另一条很有意思的路。

它不是把整个构建环境放进 Docker,而是让 Cargo 用 Zig 作为 linker。项目 README 的描述很直接:用 Zig 作为 linker 来编译 Cargo 项目,让交叉编译更容易。(GitHub)

安装:

cargo install --locked cargo-zigbuild

或者:

pip install cargo-zigbuild

后者会顺带安装 ziglang。官方 README 也列出了这两种安装方式。(GitHub)

典型使用流程是:

rustup target add aarch64-unknown-linux-gnu
cargo zigbuild --release --target aarch64-unknown-linux-gnu

对很多 Linux target 来说,它比你自己找 cross GCC、sysroot、linker 要省心不少。

它最吸引人的地方之一,是可以指定最低 glibc 版本。比如你想编一个 ARM64 Linux GNU binary,并希望兼容 glibc 2.17:

cargo zigbuild --release --target aarch64-unknown-linux-gnu.2.17

cargo-zigbuild 文档里说明,GNU target 默认 glibc 版本会随 Zig 版本变化,也支持在 target 后面加 glibc 版本后缀来指定最低版本。文档同时提醒,这个行为不一定完全等同于在真实构建宿主机上动态链接某个 glibc 版本。(GitHub)

这句话很重要。

cargo-zigbuild 能减轻 linker 和 libc 的配置压力,但它没有消灭“目标环境测试”这件事。你编出来之后,仍然应该在目标 Linux 发行版或兼容容器里跑一下。

还有几个坑要提前知道。

如果不传 --targetcargo zigbuild 实际上不会用 Zig。
某些 RUSTFLAGS,比如 -C linker,会让它绕开 Zig。
它使用 zig cc -nostdinc,所以有时找不到系统 headers 或 libraries,需要通过 CFLAGSRUSTFLAGS 等方式显式告诉它路径。
项目 README 也写明了 caveats:目前主要支持 Linux 和 macOS targets,其他目标平台需要自己确认能否工作。(GitHub)

所以我会把 cargo-zigbuild 放在一个很现实的位置:

它不是比 cross 更“高级”的方案。
它只是换了一种 tradeoff。

不用 Docker,轻。
链接 Linux GNU/musl target,舒服。
但遇到复杂 native 依赖,它还是会把问题还给你。

cross 和 cargo-zigbuild 怎么选

如果只看使用体验,两个工具都像是在帮你逃离 linker 地狱。但它们解决问题的方式完全不同。

cross 的答案是:我给你一个目标平台构建环境。
cargo-zigbuild 的答案是:我给你一个更会交叉链接的 linker。

所以选择也很清楚。

情况更推荐
团队开发,CI 要稳定复现cross
要跑跨架构测试cross
目标是 Linux GNU,且你不想手配 glibc toolchaincargo-zigbuild
不想装 Dockercargo-zigbuild
native 依赖可以通过 apt 安装cross
想控制最低 glibc 版本cargo-zigbuild
依赖非常复杂,最终部署环境固定WSL/Linux CI
纯 Rust 小项目手动 cargo build --target

举个例子。

如果你在 Windows 上写一个纯 Rust CLI,要发给 Linux x86_64 服务器:

rustup target add x86_64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-musl

够了。别上复杂工具。

如果你要发 aarch64-unknown-linux-gnu,并且项目里没有太多奇怪的 C 依赖:

rustup target add aarch64-unknown-linux-gnu
cargo zigbuild --release --target aarch64-unknown-linux-gnu

可以先试 cargo-zigbuild

如果项目依赖 OpenSSL、libpq、libsqlite3、libclang 这类东西,而且你希望 CI 和本地尽量一致:

cross build --release --target aarch64-unknown-linux-gnu

然后把依赖写进 Cross.toml

如果这是生产环境核心服务:

用 Linux CI 或 WSL 构建。
不要让发布流程建立在“我本机刚好能编过”上。

Windows 到 Linux:我的推荐路径

回到最开始的问题:Windows 上开发 Rust,能不能编译 Linux target?

能。

但我会按这个顺序做判断:

第一步,看是不是纯 Rust 项目。

如果是,先试 musl:

rustup target add x86_64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-musl

第二步,看是不是 GNU Linux target。

如果你明确要 glibc,比如要兼容某些 Linux 动态库,可以试:

rustup target add x86_64-unknown-linux-gnu
cargo zigbuild --release --target x86_64-unknown-linux-gnu

如果你还要控制 glibc 版本:

cargo zigbuild --release --target x86_64-unknown-linux-gnu.2.17

第三步,看 native 依赖复杂不复杂。

复杂的话,别继续在 Windows 宿主机上堆环境变量。用 cross,或者直接用 Linux CI。

cross build --release --target x86_64-unknown-linux-gnu

第四步,在目标环境测试。

这一步跳不过去。交叉编译成功只代表“产物被链接出来了”,不代表它一定能在你的生产机器上跑。

OpenSSL 是分水岭

Rust 交叉编译里,OpenSSL 经常是第一个让人破防的依赖。

如果你只是用 HTTP 客户端,优先考虑 Rustls:

reqwest = { version = "...", default-features = false, features = ["rustls-tls"] }

这样可以少掉很多目标平台 OpenSSL headers/libs 的麻烦。

如果你必须用 OpenSSL,那就把它当成“需要目标平台系统依赖”的项目来处理。
这时候 crosspre-build 或自定义镜像会比手动在 Windows 上拼路径靠谱。cargo-zigbuild 也可能能过,但你仍然要处理 headers 和 libraries 的查找问题。

重新整理一下结论

Rust 的交叉编译不是一句“支持”或“不支持”能说完的。

你真正要选的是构建策略。

纯 Rust 项目
  优先 cargo build --target

想少配 linker,不想用 Docker
  试 cargo-zigbuild

团队构建、多架构 CI、需要 cross test
  用 cross

依赖复杂、生产发布
  用 WSL 或 Linux CI

我现在会这样记:

rustup target add 解决 Rust 标准库。
手动 linker 解决链接。
cargo-zigbuild 用 Zig 帮你减轻链接负担。
cross 用容器帮你固定目标平台构建环境。
WSL 和 CI 让你尽量接近真实生产环境。

所以,不要只问“Windows 能不能编 Linux”。
更好的问题是:

我要的是 musl 还是 gnu?
我的项目是不是纯 Rust?
native 依赖有多复杂?
我想要本机方便,还是团队可复现?
这个 binary 最终要跑在哪个 Linux 版本上?

这些问题答清楚,工具自然就选出来了。