cargo-swift 打包 Rust → Swift 的两个隐形炸弹
用 cargo-swift 把一个 Rust crate 封装成 Swift Package(.xcframework + 自动生成的 .swift 绑定),是目前在 macOS/iOS 上复用 Rust 逻辑最流畅的方案之一。然而整个流程有两处陷阱,共同特征是:编译和链接都成功,等你跑起来才炸。
我在一个把 Rust 核心逻辑嵌入 SwiftUI 应用的项目里踩了这两个坑,记录下来省得你再走弯路。
现象
坑 A(运行时秒崩):Xcode 编译通过、链接无报错。App 启动后,第一次调用任何 Rust 导出函数,进程立即死亡,Xcode 控制台打印:
Fatal error: UniFFI contract version mismatch:
try cleaning and rebuilding your project
进程生命周期约 1 秒。更诡异的是,按照错误提示去 Clean Build Folder 再重跑,毫无作用——因为问题根本不在"脏缓存"。
坑 B(构建期 panic):在 agent、CI 或任何非交互式 shell 里执行 cargo swift package,命令直接 panic:
thread 'main' panicked ... Result::unwrap() on an Err value:
IO(Custom { kind: NotConnected, error: "not a terminal" })
如果 Rust 是通过 Homebrew 安装的(没有 rustup),还会额外报:
The following toolchains are not installed: x86_64-apple-darwin
根因
坑 A 的根因:bindgen 版本双轨
cargo-swift 内部用自己捆绑的 uniffi-bindgen 生成 Swift 绑定代码,但打包的是你 crate 里声明的 uniffi 版本编译出的 scaffolding。两者版本不一致时,FFI 合约版本号对不上,运行时直接 fatal。
错误信息建议"clean and rebuild"是误导——clean 不能解决版本不匹配的问题。
坑 B 的根因:交互式假设 + universal 构建依赖
cargo swift package 默认走交互式提示(TTY 读取),非 TTY 环境下 unwrap() 直接炸。同时它默认尝试构建 universal binary(arm64 + x86_64),Homebrew Rust 没装 x86_64 cross target,于是再炸一次。
cargo-swift 内置的 uniffi-bindgen 版本和 crate 里 [dependencies] uniffi 的版本必须小版本一致(如同为 0.31.x)。版本差一个 minor,编译链接全绿,进程活不过 1 秒。错误提示叫你 clean,但 clean 无效——这是版本问题,不是缓存问题。
正解
修坑 A:对齐 UniFFI 版本
先确认 cargo-swift 捆绑的 bindgen 版本。安装时 build log 会输出,例如:
Compiling uniffi_bindgen v0.31.1
也可查 cargo-swift 的 release notes(cargo-swift 0.11.1 → uniffi 0.31)。
然后在 crate 的 Cargo.toml 里固定相同 minor:
[dependencies]
uniffi = { version = "0.31", features = ["cli"] }
[build-dependencies]
uniffi = { version = "0.31", features = ["build"] }重新 cargo swift package + Xcode 重 build,进程不再秒崩。
升级 cargo-swift 时记得同步升 crate 侧的 uniffi 版本,保持锁步。
修坑 B:非交互式 + arm64-only 调用
cargo swift package -y --skip-toolchains-check \
--target aarch64-apple-darwin \
--platforms macos --name MyPackage --release-y(--accept-all):自动接受所有交互提示,无需 TTY。--skip-toolchains-check:跳过 x86_64 target 检测,避免 Homebrew Rust 场景报错。--target aarch64-apple-darwin:只构建 arm64,Apple Silicon 开发机上足够验证。Universal binary 留到正式发版时用rustup装全双架构再跑。
-y --skip-toolchains-check --target aarch64-apple-darwin 三个 flag 组合,让 cargo swift package 在 agent、CI、Homebrew Rust 环境下稳定运行,不再 panic 也不再因缺 x86_64 toolchain 失败。
把 Rust 打包为本地 Swift Package(SPM 本地路径引用),而不是在 Xcode Build Phase 里内嵌 cargo build。后者在 CI 上极其脆弱,环境变量、$PATH、Rust toolchain 位置一旦不对,整个 Xcode 构建就挂。预构建产物 + SPM 本地引用更稳定,也更容易缓存。
一句话外卖
cargo-swift里的uniffi-bindgen版本和你 crate 的uniffi版本必须 minor 对齐,版本差异只在运行时爆炸,编译期完全静默。