Compiling Rust Crates with C++ Dependencies in Nix
Building Rust crates that compile C++ dependencies fails mysteriously in Nix environments. The error message claims your C++ compiler doesn’t support -std=c++17
. It does. The real issue: conflicting compiler flags between Nix and the cc-rs crate.
The Error
You’re building a Rust project that depends on wasm-opt-rs
or similar crates. Everything works fine outside Nix. Inside a devenv shell:
error: failed to run custom build command for `wasm-opt-sys v0.116.0`
...
Error: C++ compiler does not support `-std=c++17` flag
The compiler definitely supports C++17. Running clang++ --version
confirms it. The build script even detects the compiler correctly. Yet it fails.
The Root Cause
The cc-rs crate automatically adds compiler flags based on target architecture. These defaults work well in standard environments but clash with Nix’s carefully controlled build settings.
When cc-rs adds flags like -mcpu=cortex-a15
and Nix provides -march=armv7-a+fp
, the compiler rejects the conflicting directives. On Apple Silicon Macs, similar conflicts occur with architecture-specific optimizations.
The cc-rs build probe tests compiler features by attempting compilation with various flags. When these conflict with Nix-provided flags, the probe fails—not because the compiler lacks C++17 support, but because the flag combination is invalid.
The Solution
Set CRATE_CC_NO_DEFAULTS=1
in your environment. This tells cc-rs to skip adding default compiler flags:
# devenv.nix
{
# Prevent cc-rs from adding conflicting compiler flags
env.CRATE_CC_NO_DEFAULTS = "1";
languages.rust.enable = true;
languages.cplusplus.enable = true;
}
The variable name is all that matters—cc-rs only checks for its existence, not its value.
Understanding wasm-opt-sys
The wasm-opt-sys
crate demonstrates this issue perfectly. It builds Binaryen’s C++ codebase from source, requiring:
- A C++ compiler with C++17 support
- No CMake (intentionally, to keep builds simple)
- Complex flag management for cross-platform builds
During the build, cc-rs runs compiler detection probes. With default flags enabled, these probes fail in Nix environments due to flag conflicts. The error message about C++17 support is misleading—it’s really about incompatible flag combinations.
Why This Works
By disabling cc-rs defaults, you allow Nix to maintain complete control over compiler invocation. Nix provides:
- Proper sysroot configuration
- Architecture-specific optimizations
- Reproducible build flags
The cc-rs crate still handles compiler detection and invocation but skips the problematic default flags. This separation of concerns resolves the conflict.
When You Need This
Set CRATE_CC_NO_DEFAULTS=1
when:
- Building crates with C/C++ dependencies in Nix
- Encountering mysterious compiler capability errors
- Cross-compiling in Nix environments
- Using devenv.sh or similar Nix-based development tools
Common affected crates include:
wasm-opt-sys
/wasm-opt-rs
ring
(cryptography)tree-sitter
language parsers- Any crate using cc-rs for native compilation
Alternative Approaches
If setting the environment variable doesn’t work:
- Check compiler detection: Ensure
languages.cplusplus.enable = true
in devenv.nix - Verify toolchain: Nix should provide clang or gcc with proper wrappers
- Debug cc-rs: Set
CC_ENABLE_DEBUG_OUTPUT=1
to see detailed compiler invocation
For cargo-based solutions without devenv:
CRATE_CC_NO_DEFAULTS=1 cargo build
Or in .cargo/config.toml
:
[env]
CRATE_CC_NO_DEFAULTS = "1"
The Broader Pattern
This issue extends beyond wasm-opt-sys. Any Rust crate that:
- Uses cc-rs to compile native code
- Targets multiple architectures
- Runs in controlled build environments
Can hit this flag conflict. The Yocto Project officially adopted CRATE_CC_NO_DEFAULTS
for all Rust builds, confirming this is a widespread pattern.
The conflict arises from different philosophies: cc-rs assumes it should optimize for the target platform, while Nix assumes it controls the entire build. Both are reasonable positions that clash in practice.
Understanding this pattern helps diagnose similar build failures quickly. When a build claims your compiler lacks basic features it clearly supports, check for flag conflicts first.