Skip to content

Rust 1.99 stabilizes C-variadic function definitions

Rust 1.99.0, out October 1, 2026, lets stable Rust define C-variadic functions like printf, moves to LLVM 23 and makes no_mangle on generics an error.

By Tech AI Wire Team

4 min read

XLinkedIn
Screenshot of the Rust Blog post 'Announcing Rust 1.99.0', dated October 1, 2026, from the Rust Release Team.

By the numbers

new code-generation backend
LLVM 23
average Rustdoc speedup on trait impl filtering
20%
best-case Rustdoc speedup on some crates
40%
release that first warned about no_mangle on generics
1.56

The Rust team released Rust 1.99.0 on October 1, 2026. Its headline change lets stable Rust define C-variadic functions, the kind of function that takes any number of arguments, like C's printf. That closes a gap for Rust code that has to work hand in hand with C.

The release also moves the compiler to LLVM 23, speeds up the documentation tool and turns one long-standing warning into a hard error.

What C-variadic definitions add

A variadic function accepts a variable number of arguments. C uses them widely, and Rust has long been able to call them. Defining one in Rust was another matter. "Rust could already call externally-defined variadic functions (e.g., libc::printf). With Rust 1.99, these functions can now be written in Rust itself," the official announcement says.

The syntax uses ... as the last parameter. ByteIOTA gives the shape of a definition as unsafe extern "C" fn my_logger(count: u32, mut args: ...) -> i32. Inside the function, the extra arguments arrive as a core::ffi::VaList, which the Rust blog says is compatible with C's va_list type at the binary level. Linux Compatible reports that code reads each argument with next_arg::<T>().

A trait called VaArgSafe guards which types can pass through. It marks the types that are safe to read from the argument list, which helps prevent type mismatches. The release also lets naked functions use variadic signatures, according to the Rust blog and releases.rs. A naked function is one the compiler emits with no setup code, which low-level and embedded code uses to control the calling convention exactly.

Newly stable library APIs

The Rust blog lists these among the APIs now available on stable:

  • Memory layout: Layout::for_value_raw, mem::size_of_val_raw, mem::align_of_val_raw
  • Collections and boxes: Box::into_non_null, Vec::into_parts, Vec::from_parts, VecDeque::retain_back
  • File system: std::fs::set_times, std::fs::set_times_nofollow

The set_times functions change a file's timestamps from the standard library. As its name suggests, the _nofollow variant does not follow symbolic links.

Compiler, platform and tooling changes

Rust 1.99 upgrades to LLVM 23, the code-generation library that turns Rust into machine code. ByteIOTA reports a 1.2% mean cut in compile wall time across benchmarks, part of a 4.57% gain over two months.

Other changes, from Linux Compatible and releases.rs:

AreaChange
PlatformsRISC-V 64-bit Linux with musl moves up to Tier 2 support
LinkingStatic position-independent executables are enabled on GNU and musl targets
RustdocFiltering trait implementations is about 20% faster on average, up to 40% on some crates
MacrosAttribute macros now work on outlined modules, such as #[my_macro] mod foo;
Inline assembly128-bit integers can be passed in vector registers on x86

Cargo, Rust's build tool, gets three changes. It reserves a new debug profile, which releases.rs describes as preparation for moving the dev profile away from debugging "to give a saner default." It turns off incremental compilation when the CI environment variable is set, and ByteIOTA notes that CARGO_INCREMENTAL=1 turns it back on. Workspace members on the 2024 edition can also override inherited dependency defaults.

What could break

The release carries several compatibility changes:

ChangeDetail
#[no_mangle] on generic itemsNow a hard error; it had warned since Rust 1.56, per ByteIOTA
Legacy integral modulesNow fully deprecated
Extern staticsBorrows no longer promote to 'static automatically, ByteIOTA reports
Doc-comment code blocksAttributes in them must have valid targets, or the doc build fails
RangeInclusiveIterating it is better optimized, which changes the behavior of an already-exhausted range
Pin::new_uncheckedIts safety rules are spelled out more precisely

This is the latest step in a busy year for the language. In August, Rust stabilized the never type after ten years, and nightly builds switched on the next-generation trait solver.

What this means for developers

Delete your printf shims. If you wrote C code, or used a nightly feature, just to accept variadic arguments from a C caller, you can now do it in stable Rust. Plugin hosts, custom allocators and logging bridges are the obvious places to look.

Check generic code for #[no_mangle] before you upgrade. It has warned since Rust 1.56, so a crate that still compiles with that warning will now fail. Search your codebase and your vendored dependencies for the attribute on any generic function.

Watch your CI build times. Cargo now turns off incremental compilation when CI is set. That is usually the right call for clean builds, but a CI setup that caches the target directory between runs may get slower. Set CARGO_INCREMENTAL=1 if your own timings say so.

Run your doc tests. Stale attributes in code blocks inside doc comments now fail the doc build. Run cargo test --doc early so a broken example does not block a release.

Test code that reuses finished ranges. The RangeInclusive change only affects ranges that have already been iterated to the end. Code that keeps using such a range after the loop should be checked against the new behavior.

Sources

  1. Announcing Rust 1.99.0 - Rust Blog
  2. Rust 1.99.0 Ships with Stable C-Variadic Functions - Linux Compatible
  3. Rust 1.99 Is Out: C-Variadics and What Breaks - ByteIOTA
  4. 1.99.0 - releases.rs

Related articles

The daily brief

Three to five stories a day, and what each one means for the people who build software. Free, no spam.