Rust 1.97 turns on v0 symbol mangling by default and stops hiding linker output

Rust 1.97.0 made v0 mangling the stable default, gave Cargo a real build.warnings setting, and stopped silencing linker output on success. 1.97.1 fixed a miscompilation a week later.

Younes Bekrar7 min read
ShareXLinkedInFacebook
Rust 1.97 turns on v0 symbol mangling by default and stops hiding linker output

No new syntax anyone will brag about. Rust 1.97.0 landed July 9 with three plumbing changes that production shops will notice on the first toolchain bump: v0 symbol mangling is now the stable default, rustc stopped swallowing linker stderr when a link succeeds, and Cargo grew a real `build.warnings` knob, allow / warn / deny, so CI can fail on warnings without the `RUSTFLAGS=-Dwarnings` hammer that nukes the build cache every time someone flips it. A week later, 1.97.1 patched an LLVM miscompilation. That sequence is the story.

v0 mangling finally leaves nightly

Every function and static needs a globally unique linker symbol. Generics wreck source names, so the compiler folds in paths, crate identity, type params. For years that mangling borrowed the Itanium ABI C++ tools already knew, fine for old demanglers, clumsy for Rust-only constructs, with generics often smashed into a hash. RFC 2603 sketched a Rust-specific scheme in 2018. Opt-in as v0 since 1.59. Nightly default since November 2025. 1.97 makes it stable's default. Legacy mangling is nightly-only now, headed for removal. Eight years from RFC. Slow on purpose. Botch a symbol format and your 2 a.m. Stack trace is gibberish.

Cargo.toml
toml
[lints.rust]linker_messages = "allow"
# Or, for CI that should fail on warnings without# invalidating the build cache the way RUSTFLAGS does:# CARGO_BUILD_WARNINGS=deny cargo test --keep-going

App code shrugs. Custom crash symbolizers and binary-size tools that parsed the old Itanium-ish strings may not. GitHub's 1.97.0 notes say as much. Rustc-demangle / rustc's own demangling are safer than a home-rolled parser. I'd run one incident-response drill against a 1.97 binary before trusting the pager. Symbolizers that "mostly work" until the one crash that matters are worse than ones that fail loudly in staging.

I still find it funny that the loudest reaction inside some Slack channels was about backtrace aesthetics, not about the RFC timeline. Fair, I guess, aesthetics is what you see. The eight-year path is what kept that aesthetic from becoming a migration nightmare.

Cargo warnings, chatty linkers, quieter stabilizations

`RUSTFLAGS=-Dwarnings` works and is blunt: flip it and you often invalidate the cache mid-refactor. `build.warnings` is Cargo-owned, so `CARGO_BUILD_WARNINGS=allow` can quiet noise while you chase real errors, then deny goes back in CI without a full flush. Pair deny with `--keep-going` and you collect every failure instead of dying on package one. If you've ever toggled RUSTFLAGS in a matrix build and watched sccache cry, you already know why this setting exists.

rustc used to hide linker stderr on successful links. That hid real bugs with the noise. 1.97 surfaces it as a `linker_messages` warning lint and filters known false positives. Several nightly defects got fixed just because people could finally see the messages. Catch: `linker_messages` is not in the warnings group, platform-dependent linker chatter rustc doesn't fully own. Turn on deny-warnings and a chatty linker in the same PR and you'll debug CI ghosts all morning. Sequence them, or set `linker_messages = "allow"` in `Cargo.toml` as in the snippet above.

Also stabilized: `cfg(target_has_atomic_primitive_alignment)` for no_std folks laying out atomics next to C or hardware. Five CPU features (div32, lam-bh, lamcas, ld-seq-sa, scq) on stable feature detection. `dead_code_pub_in_binary` for unused `pub` in bins that aren't APIs. And `Result<T, Uninhabited>` / `ControlFlow<Uninhabited, T>` treated like `T` for `must_use`. Small stuff. Fine. The `pub`-in-binary lint alone will annoy a few codebases that used `pub` as a habit rather than an API boundary, which is the point.

Since Rust 1.59, the compiler has supported opting into a Rust-specific mangling scheme via -Csymbol-mangling-version=v0. Since November 2025, this scheme has been enabled by default on nightly, and 1.97 is now enabling it on stable Rust.
Rust Release Team, announcing Rust 1.97.0

1.97.1, and the libssh2 CVEs hiding in 1.96.1

July 16: 1.97.1. LLVM miscompilation. Blog and release notes are candid, the LLVM bug had been around since at least 1.87. A 1.97.0 change in how some enum discriminants were represented made the bad path much likelier. LWN walked the mechanics: an LLVM pass turning two conditional loads into a load of a selected pointer, fine for true/false, https://images.unsplash.com/photo-1550751827-4bd374c3f58b?auto=format&fit=crop&w=1600&q=80 when the condition is poison, which is how a −1 discriminant could become a gigabyte-scale OOB read. Fix: LLVM submodule backport, plus a cautious revert of the rustc-side trigger even though notes say the revert alone wasn't strictly required. Point releases like this (1.91.1, 1.93.1, 1.94.1, 1.96.1…) are why people install Rust patches the week they ship. If you're on 1.97.0, move.

I don't love how easy it is to skim a "miscompilation" note and assume it only hits exotic code. Poison + selected pointers is the kind of path that shows up in real enum-heavy codebases. LWN's write-up is worth the time if you maintain anything that lived on 1.97.0 for those seven days.

For a lot of companies the juicier recent patch is actually 1.96.1 (June 30): three libssh2 CVEs in the copy Cargo uses for SSH Git, CVE-2025-15661, CVE-2026-55199, CVE-2026-55200, plus an HTTP retry fix and a MIR miscompilation repair. Private deps over SSH? Confirm you're past 1.96.1 before you treat the upgrade as a language-features errand. I've seen security reviews ask about application crates and forget the toolchain's own SSH stack. That's the miss.

Early August stable is 1.97.1. Beta 1.98 and nightly toward 1.99 already. No source edits required for the 1.97 defaults. Cargo warnings stay opt-in. TechTimes and the notes both flag crates that leaned on a `pin!()` coercion bug from 1.88, those fail to compile after upgrade, which is the compiler catching a mistake rather than a migration tax. 2026's Rust releases keep leaning into plumbing over headline syntax. You feel 1.97 when the profiler opens a stack trace, when CI stops torching the cache to deny warnings, and when the linker finally says what it was muttering. Not glamorous. Preferable to another syntax sugar cycle nobody asked for.

Upgrade order I'd actually use

If a team asked me how to take 1.97 without inventing drama: land on 1.97.1, not 1.97.0. Confirm Cargo is past the libssh2 CVEs. Exercise one crash-report path against a v0-mangled binary. Turn on linker_messages visibility in a canary before deny-warnings CI. Then adopt `build.warnings=deny` once the linker is quiet. No checklist section needed beyond that, and no, you do not need a six-week "mangling migration project" unless you wrote a custom demangler you shouldn't have.

Everything else in 1.97 is nice-to-have for specialized code. The release is plumbing. Treat it like plumbing. The teams that will feel it most are the ones with custom symbolizers, chatty linkers in CI, and private crates over SSH, not the ones waiting for a new language feature to justify the bump.

Six-week cadence continues. Beta 1.98 is already in flight. If 1.97 taught anything, it's that the interesting Rust work right now is in the toolchain's honesty about what it used to hide, linkers, caches, and SSH libraries included. Upgrade for those reasons. Skip the release notes if you only skim for new syntax. You'll miss the parts that actually matter to production CI, crash reporting, and those inevitable late-night incident response pages.

  • Open Source

Keep reading