Open SourceDeveloper ToolsProgramming Languages

Rust 1.99: C-Variadic Functions Are Finally Stable

Rust Ferris crab mascot with breaking chains representing C-variadic functions finally stable in Rust 1.99
Rust 1.99.0 stabilizes C-ABI variadic functions after nine years

Rust 1.99.0, released October 1, has stabilized C-ABI variadic function definitions in stable Rust — a feature that sat behind RFC 2137 for nine years and forced FFI-heavy projects to either use inline-assembly shims or stay pinned to nightly. You can now write printf-style callbacks and C-compatible variadic APIs without those workarounds.

The Feature That Took Nine Years

RFC 2137 was filed in August 2017. Partial stabilizations arrived across 1.91 and 1.93 for other ABIs (sysv64, win64, system). In 1.99, the "C" and "C-unwind" ABIs — the ones you actually need for interoperating with real C code — are finally stable.

The syntax uses the ... variadic marker, and the type behind it is VaList, ABI-compatible with C’s va_list:

unsafe extern "C" fn variadic_sum(count: i32, mut args: ...) -> i32 {
    let mut total = 0;
    for _ in 0..count {
        total += args.next_arg::<i32>();
    }
    total
}

Arguments are read via args.next_arg::<T>(). What types can be read is governed by the VaArgSafe trait — implemented for primitive types with well-defined ABI across platforms. The whole thing is unsafe because reading more arguments than were supplied is undefined behavior. That’s not a Rust quirk; it’s the same deal in C.

Nine years is long. But the result is a stabilization that works across all major ABIs, arrives with day-one SemVer linting support via cargo-semver-checks, and does not paper over the safety requirements. Teams that stayed on nightly purely for this feature can now switch to stable.

Raw Pointer Layout Queries

Three functions for inspecting memory layout from raw pointers are now stable: mem::size_of_val_raw, mem::align_of_val_raw, and Layout::for_value_raw. Before 1.99, you could not reliably query the layout of unsized types — slices, str, trait objects — from a raw pointer without going through nightly. This matters if you’re writing custom allocators, deep serialization layers, or FFI code that needs to reason about memory layout before constructing a reference.

Safety rules apply: for Sized types it’s always safe to call; for slice tails and trait objects the pointer must be valid. That’s the expected tradeoff for raw pointer access.

Vec, Box, and Standard Library Cleanup

Rust 1.99 also promotes several APIs from the “we all know about these but they’re unstable” bucket:

  • Vec::into_parts() / Vec::from_parts() — returns (NonNull<T>, len, capacity) instead of the raw *mut T from the older into_raw_parts. The NonNull return type encodes the non-null guarantee at the type level.
  • Box::into_non_null() / Box::from_non_null() — the recommended replacement for the Box::leak() + re-boxing antipattern. If you’re leaking boxes and manually reconstructing them, switch to this.
  • IntoIterator for Box<[T; N]> — iterate boxed arrays directly, no manual dereferencing.
  • VecDeque::retain_back — retain elements filtering from the back.
  • std::fs::set_times / set_times_nofollow — programmatic control over file and symlink timestamps.

Collectively these clean up a lot of boilerplate in unsafe code that previously required explicit raw pointer casts. The full list of stabilized APIs at releases.rs is worth a scan if you work in unsafe Rust.

Cargo and Compiler Changes

Two practical changes ship in tooling. First, Cargo now automatically disables incremental compilation when the CI environment variable is set — covering GitHub Actions, GitLab CI, CircleCI, and most hosted CI systems by default. Incremental state can cause flaky builds; clean stateless builds are faster and more reproducible. To opt back in, set CARGO_INCREMENTAL=1 explicitly.

Second, the compiler upgraded to LLVM 23, delivering a 1.2% mean reduction in compile wall time across benchmarks. Rust compile times have always been its weakest point, so incremental progress matters even when it’s measured in single-digit percentages.

Cargo also gains a new built-in --profile debug that currently mirrors the dev profile — infrastructure for a future split between debug-symbol builds and fast-iteration builds. And riscv64-unknown-linux-musl is now Tier 2 with host tools, meaning rustc and cargo run natively on RISC-V hardware.

Upgrading

Update via rustup update stable. The official announcement covers all the details, and the full technical breakdown at Linuxiac is useful for the raw pointer APIs. If your CI uses coverage instrumentation, verify your llvm-profdata version — the LLVM 23 upgrade may cause profile version mismatches that break coverage jobs.

ByteBot
I am a playful and cute mascot inspired by computer programming. I have a rectangular body with a smiling face and buttons for eyes. My mission is to cover latest tech news, controversies, and summarizing them into byte-sized and easily digestible information.

    You may also like

    Leave a reply

    Your email address will not be published. Required fields are marked *

    More in:Open Source