Rust 1.99.0 stabilizes C variadics and raw-pointer layout queries
Rust 1.99.0, announced by the Rust Release Team on October 1, 2026, is a stable release that pushes the language forward in three areas that matter to systems programmers: foreign function interfaces, low-level memory introspection, and the practical guidance around ownership escape hatches. The headline feature is the stabilization of defining C-ABI variadic functions with the "C" and "C-unwind" ABIs, closing a long-standing gap between calling external variadic C APIs and implementing them in Rust. Alongside that, the release stabilizes APIs for retrieving layout information from raw pointers to both sized and non-sized types, and it updates the documentation for Box::leak to steer developers away from patterns that can interact badly with compiler optimizations and future allocator work.
These changes are not flashy syntax experiments. They are the kind of incremental, carefully specified improvements that make Rust more capable at the boundaries where systems code lives: calling conventions, raw memory, allocator contracts, and interoperability with C libraries. For teams building operating system components, embedded runtimes, database engines, language bindings, or performance-sensitive infrastructure, Rust 1.99.0 removes a few sharp edges that previously required unstable features, unsafe workarounds, or awkward C shims.
A release focused on the FFI boundary
Rust has long been able to call externally defined variadic functions such as libc::printf. That capability made it possible to integrate with large parts of the C ecosystem, but it left an asymmetry: Rust code could consume variadic C APIs more easily than it could define them. Rust 1.99.0 addresses that by stabilizing C-variadic function definitions. According to the Rust Blog announcement, variadic functions defined this way use a variable argument list (...) and accept an arbitrary number of arguments.
The example in the release announcement shows the shape of the feature clearly:
/// SAFETY: must be called with (at least) 2 i32 arguments.
unsafe extern "C" fn sum(mut args: ...) -> i32 {
// SAFETY: guaranteed by the caller.
let a = unsafe { args.next_arg::<i32>() };
let b = unsafe { args.next_arg::<i32>() };
a + b
}
fn foo() -> i32 {
unsafe { sum(0i32, 2i32) }
}
This snippet is small, but it communicates the important design choices. The function is declared as unsafe extern "C", reflecting that the C ABI and the caller’s argument contract are outside Rust’s usual static guarantees. The ... parameter yields a VaList, and reading arguments from that list is also unsafe because the implementation must trust that the caller passed the expected types in the expected order. The release notes state that the type of ... is VaList, which is ABI-compatible with the C va_list type across targets, and that what types can be read from a VaList is guarded by the VaArgSafe trait.
For systems developers, the practical benefit is straightforward: when a C callback interface or plugin ABI expects a Rust-defined function with C variadic behavior, it can now be expressed directly in stable Rust. That reduces the need to maintain a thin C wrapper whose only purpose is to receive variadic arguments and forward them in a more structured form. Fewer wrappers mean fewer cross-language build steps, fewer places for ABI assumptions to drift, and less duplicated safety reasoning.
Why C variadics are hard, and why stabilization matters
Variadic functions are convenient at the API surface but notoriously unforgiving underneath. In C, the callee typically cannot inspect how many arguments were passed or what their types are. The format string in printf, an explicit count, or a sentinel value usually carries that information, and the program is responsible for keeping the convention consistent. Rust’s stabilization does not magically make that contract safe; it makes the mechanism available in a way that fits Rust’s explicit unsafe model.
That distinction is important. The unsafe keyword on the function definition and on argument extraction is not ceremony. It signals that correctness depends on an external protocol. A caller that passes the wrong number of arguments, omits a terminator, or provides values of the wrong type can trigger undefined behavior, just as it would in C. The value of the Rust feature is that the boundary is now visible and implementable in the language used by the rest of the project, with standard library types such as core::ffi::VaList participating in the interface.
The detailed release notes for 1.99.0 add another relevant stabilization: Rust now supports using #[unsafe(naked)] functions to define C-variadic functions, tracked under the former c_variadic_naked_functions feature. Naked functions are a specialized tool for low-level control over prologue and epilogue generation, and combining them with C variadics is an even narrower use case. Most application code will never need this combination. But for runtime implementers, ABI adapters, or platform-specific glue, the stabilization broadens what can be expressed in stable Rust without dropping into separate assembly files or foreign-language helpers.
Layout information from raw pointers
The second major theme in Rust 1.99.0 is raw-pointer layout introspection. The release announcement states that this release settles the safety requirements for retrieving the size and alignment on raw pointers to both Sized and non-Sized types. It does so by stabilizing three functions: Layout::for_value_raw, mem::size_of_val_raw, and mem::align_of_val_raw.
On the surface, size and alignment queries sound mundane. In practice, they sit underneath allocators, custom collections, arena implementations, foreign object wrappers, and unsafe abstractions that need to reason about memory without holding a safe reference. Before this stabilization, developers working with raw pointers and dynamically sized types often had to rely on less direct patterns or unstable APIs to obtain layout information. The stable APIs make the intent clearer and provide an official surface for a task that low-level libraries already perform.
The nuance is in the words “settles the safety requirements.” Raw pointers do not carry Rust’s borrowing guarantees. Asking for the layout of the value behind a pointer can be straightforward for a sized type, but non-sized types such as slices and trait objects require metadata. That means callers must ensure the pointer and its metadata are valid for the operation being performed. Stabilization here is therefore not just about convenience; it is about documenting and committing to the rules that unsafe code must follow.
For systems programmers, this matters because allocator-facing code is where small misunderstandings become memory corruption. A custom allocator that computes the wrong size for a tail slice, a container that misreads alignment for a dynamically sized element, or an FFI layer that guesses the layout of a foreign object can all fail in ways that are difficult to reproduce. The presence of core::alloc::Layout::for_value_raw in stable Rust gives such code a standard path, and it gives reviewers a concrete API contract to evaluate.
Box::leak guidance: semantics unchanged, advice clarified
The release announcement is careful about the third highlighted item: there are no changes to the language semantics in Rust 1.99.0, but the documentation on Box::leak has been updated to recommend against patterns that later deallocate that memory. The Rust team explains that such code was found to have problematic interactions with current and future potential compiler optimizations, and is especially problematic with the upcoming stabilization of custom allocators. Instead, Box::into_non_null or Box::into_raw should be preferred when the goal is to move ownership into a raw pointer representation that may later be reconstructed and dropped.
This is a subtle but meaningful piece of guidance. Box::leak has a legitimate use: intentionally preventing deallocation, often to obtain a 'static reference or to hand ownership to a runtime that never releases it. The problematic pattern is different. It treats leaking as a temporary conversion step, takes the resulting reference or pointer, and later attempts to recover ownership and free the allocation. That round trip can look plausible in local code, especially when developers are trying to bridge between owned Rust values and raw-pointer APIs. The updated guidance says, in effect, that if you intend to deallocate, you should use APIs designed for ownership transfer rather than an API whose name and semantics communicate permanent leakage.
The stabilization of Box::into_non_null and Box::from_non_null in Rust 1.99.0 fits neatly with this recommendation. Vec::into_parts and Vec::from_parts were also stabilized, giving another example of the same broader direction: explicit, structured ownership transitions are preferable to ad hoc tricks. For library authors, this is a good moment to audit unsafe internals that may have used leak/unleak patterns and replace them with conversions that preserve the intended provenance and ownership story.
Stabilized APIs and library additions worth noting
Beyond the headline items, Rust 1.99.0 stabilizes a collection of APIs that will quietly improve everyday systems and application code. The Rust Blog lists IntoIterator implementations for Box<[T; N]>, &Box<[T; N]>, and &mut Box<[T; N]>, which make boxed arrays easier to consume in iterator-based code. VecDeque::retain_back adds a counterpart to existing retention patterns for double-ended queues. String::from_utf8_lossy_owned and string::FromUtf8Error::into_utf8_lossy provide more ergonomic paths when handling owned byte data that may not be valid UTF-8.
Several of the stabilized items are especially relevant to low-level code. core::ffi::VaList is the public face of the C-variadic feature. The raw layout functions mentioned earlier are now stable in core::mem and core::alloc. std::fs::set_times and std::fs::set_times_nofollow add file timestamp control to the stable standard library. FusedIterator for StepBy<I> is a smaller iterator trait guarantee, but iterator fusion can matter in generic code and adapter composition.
The detailed release notes also show that 1.99.0 is broader than the blog’s top highlights. The language section includes changes such as a new allow-by-default raw_borrows_via_references lint, an extension of the unconditional_panic lint to function calls that panic when chunk or window sizes are zero, stabilization of #[my_macro] mod foo; for outlined modules in custom attribute and derive macros, and stabilization of passing 128-bit integers via vector registers with asm! on x86. There are also compiler, library, Cargo, rustdoc, and compatibility changes that will affect toolchains and builds in practical ways.
Cargo, rustdoc, and compatibility details that teams should read
For many projects, the language features are less disruptive than the tooling and compatibility notes. Cargo 1.99.0 adds a new built-in debug profile as preparation for transitioning the dev profile away from debugging toward faster development iterations; the release notes say there is currently no difference between dev and debug. Cargo also changes workspace dependency behavior on edition 2024 or later: workspace members can override an inherited workspace dependency’s default-features field, such as serde = { workspace = true, default-features = false }. On earlier editions, default-features = false is ignored with a warning. These are the kinds of changes that can alter dependency resolution or feature unification expectations in large workspaces, so they deserve attention during upgrades.
Rustdoc 1.99.0 adds an unused_footnote_definition lint and reports substantial performance improvements in trait implementation filtering, with gains described as 20% on average and up to 40% on some real-world crates. Faster documentation builds are not merely a quality-of-life improvement; for large projects and CI pipelines, they reduce feedback time and resource cost.
The compatibility notes are equally concrete. The release deprecates legacy integral modules such as std::i32::MAX in favor of associated constants like i32::MAX. It upgrades no_mangle_generic_items into a hard error. It clarifies and changes several behaviors around layout, attributes, unsafe code patterns, and macro lints. One operationally significant Cargo note states that incremental compilation is now disabled by default when running in CI, detected via the CI environment variable. Teams that relied on CI incremental behavior should check build times and cache strategies after upgrading.
The 1.99.1 point release and upgrade hygiene
Two weeks after 1.99.0, the Rust team published Rust 1.99.1 on October 15, 2026. Point releases are easy to overlook, but this one fixes regressions that matter to specific targets and FFI use cases. The Rust Blog states that 1.99.1 resolves a missing wcslen symbol on some targets, including UEFI and embedded cases where LLVM’s loop-idiom-wcslen pass can introduce calls that are not otherwise provided. It also fixes a miscompilation of FFI bool return values on x86_64, where Rust functions returning bool through FFI could leave bits 1-7 of the return register unspecified; the return type is now marked for zero-extension again as required by the ABI. Finally, it fixes a rust-lld issue on macOS by adding an rpath entry for the directory containing libLLVM.dylib.
For anyone adopting the 1.99 line, the lesson is simple: read the main release announcement for features, then read the point release notes before rolling out widely. The FFI bool fix is particularly relevant in mixed-language codebases, where an ABI detail can silently produce wrong behavior rather than an obvious build failure. The UEFI and wcslen fix is a reminder that compiler optimizations, target libraries, and standard library builds interact in ways that only show up on specific platforms.
Conclusion
Rust 1.99.0 is a practical systems release: it stabilizes C-ABI variadic function definitions, makes raw-pointer layout queries available through stable VaList-adjacent FFI support and raw layout APIs, and clarifies how Box::leak should be used in light of compiler and allocator realities. The broader 1.99.x line also matters for upgrade safety, as Rust 1.99.1 quickly fixed regressions involving wcslen, x86_64 FFI bool returns, and rust-lld on macOS.
For maintainers of FFI-heavy crates, unsafe libraries, and multi-platform toolchains, the right next step is to review the release notes against local assumptions: replace C variadic shims where appropriate, prefer Box::into_raw or Box::into_non_null for ownership transfer, and test targets affected by the 1.99.1 fixes. Rust’s progress here is incremental, but it removes real friction at the boundaries where reliable systems software is hardest to write.
Sources
- Announcing Rust 1.99.0 — Rust Blog, Rust Release Team, October 1, 2026.
- Rust 1.99.0 release notes — Rust documentation.
- Announcing Rust 1.99.1 — Rust Blog, Rust Release Team, October 15, 2026.