I have struggled with RUSTFLAGS more than necessary recently and would like to argue that the defaults as very suboptimal. They make simple things unnecessarily hard for something that I don't think is inherently complex. To be clear, there is a lot of inherent complexity with RUSTFLAGS, but most common cases should not have to touch it and should be much simpler.
Motivating example 1
I was recently helping to enable AVX512 support on Rust stable in curve25519-dalek. Unfortunately, this caused an issue with intermittently available AVX512-capable GitHub Actions runners.
The gist of it is that CI looked like this:
test-simd-nightly:
name: Test simd backend (nightly)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@nightly
- env:
# This will:
# 1) build all of the x86_64 SIMD code,
# 2) run all of the SIMD-specific tests that the test runner supports,
# 3) run all of the normal tests using the best available SIMD backend.
# This should automatically pick up the simd backend in a x84_64 runner
RUSTFLAGS: '-C target_cpu=native'
run: cargo test --target x86_64-unknown-linux-gnu
And failed like this:
error[E0433]: cannot find `spec_avx512ifma_avx512vl` in `precomputed_straus`
--> curve25519-dalek/src/backend.rs:105:49
|
105 | vector::scalar_mul::precomputed_straus::spec_avx512ifma_avx512vl::VartimePrecomputedStraus,
| ^^^^^^^^^^^^^^^^^^^^^^^^ could not find `spec_avx512ifma_avx512vl` in `precomputed_straus`
|
note: found an item that was configured out
--> curve25519-dalek/src/backend/vector/scalar_mul/precomputed_straus.rs:18:9
|
14 | / #[curve25519_dalek_derive::unsafe_target_feature_specialize(
15 | | "avx2",
16 | | conditional("avx512ifma,avx512vl", curve25519_dalek_backend = "avx512")
| |_____________________________________- the item is gated behind the `avx512ifma` feature
17 | )]
18 | pub mod spec {
| ^^^^
The reason for this is that while the library was compiled with RUSTFLAGS, doc tests were not. So documentation tests were compiled against non-existent API. The solution today is to set RUSTDOCFLAGS to the same value.
I'd argue this behavior makes no sense and is very confusing to someone who have not seen this before. Tests should always run against the same API the crate was compiled with, and RUSTDOCFLAGS should be a strict super-set of RUSTFLAGS as the result.
Motivating example 2
I've been struggling with enabling of -Znext-solver=globally and as of today (nightly-2026-07-08) it is still impossible to actually do fully:
- Can't specify `-Znext-solver=globally` for proc macros under Miri - The Rust Programming Language Forum
- `-Zhost-config` breaks `cargo miri test` on proc macros under Miri · Issue #5101 · rust-lang/miri · GitHub (see older version of the initial post about the original report or else discussion will make little sense, sorry)
This one boils down to the fact that there are separate RUSTFLAGS, RUSTDOCFLAGS and MIRIFLAGS, only two of which can be specified in .cargo/config.toml:
- Set MIRIFLAGS from .cargo/config.toml · Issue #2347 · rust-lang/miri · GitHub
- Support `miriflags` in `[target]` configuration · Issue #17079 · rust-lang/cargo · GitHub
If that wasn't bad enough, especially when cross-compilation is involved, host and build targets are not the same anymore, so even more configuration is necessary to configure both targets to have -Znext-solver=globally specified on them.
So now I have this in my .cargo/config.toml:
# This wildcard allows to merge whatever user might have configured in their home folder
[target.'cfg(all())']
rustflags = ["-Znext-solver=globally", "-Zmin-recursion-limit=256"]
rustdocflags = ["-Znext-solver=globally", "-Zmin-recursion-limit=256"]
Plus MIRIFLAGS="-Znext-solver=globally -Zmin-recursion-limit=256".
Plus -Ztarget-applies-to-host -Zhost-config --config 'host.rustflags=["-Znext-solver=globally"]' in CLI arguments.
GitHub Actions quirk
Which depending on the context might force user to learn that ' sometimes needs to be escaped as ''' in GitHub Actions YAML definition, making it this:
-Ztarget-applies-to-host -Zhost-config --config '''host.rustflags=["-Znext-solver=globally"]'''
Only then I can actually set it everywhere (that is once it actually works as expected, which I hope is going to be soon).
Man, I just want to set RUSTFLAGS="-Znext-solver=globally" and be done with it ![]()
This is very bad experience.
The solution
Ideally, I'd like for RUSTFLAGS to become a global recursively applied environment variable. Meaning it applies to host target and build target, to both normal crates, build scripts, proc macros, rustdoc, Miri - everything.
Something like RUSTHOSTFLAGS and RUSTBUILDFLAGS would then be a narrow equivalent of whatever RUSTFLAGS roughly means today.
Though it might be impossible to do due to inherent breaking changes and lots of inherent complexity I'm not aware of.
At least something like RUSTGLOBALFLAGS, which is an additive super-set of all possible RUST*FLAGS, would elegantly solve both of the motivating examples above.
It would not be universal in a sense of covering all possible edge-cases, but I think it might just solve the majority of use cases users have for RUSTFLAGS in practice because the current state is honestly a mess. And only when user does things that actually require divergence between various flags they would have to learn that there is actually a family of different environment variables with a lot of tricky nuances in how they function.