Global, simple, universal RUSTFLAGS

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:

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:

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 :sob:

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.

3 Likes

The real solution is to move all of these options to Cargo natively.

3 Likes

Tracking replacement discussions at Tracking Issue: Exposing RUSTFLAGS in cargo · Issue #12739 · rust-lang/cargo · GitHub

1 Like

Cargo support is useful and will certainly be appreciated. It will also remove the need to use RUSTFLAGS in certain cases completely.

That said, I'm not sure it is unrealistic to expect everything supported in RUSTFLAGS to be supported in Cargo directly. And as long as that is the case, I think my suggestion is still relevant.

Also see

Unfortunately this would be a breaking change to do now...

Expecting everything to be in Cargo also doesn't help when other build systems are used (such as meson). This makes it very hard to globally configure options for package building on a Linux distro level. See this Arch Linux issue for some of the problems this cause.