On this page · 6 sections
- Native C-ABI Variadics: Defining Variable Argument Functions in Rust
- Layout Introspection from Raw Pointers
- Recommendations Against Round-Trip Unleaking After Box::leak
- Toolchain Demotions: 32-Bit Windows Shift to Target-Only Support
- Security Advisory: Remediating Miri Target Cache Leakage in CI
- Strengthening Ecosystem Maintenance: Cargo Maintainer in Residence
- Sources
Modern systems programming demands an ongoing balance between low-level interface parity, robust memory semantics, and reliable build infrastructure. With the release of Rust 1.99.0 and the structural target adjustments scheduled for Rust 1.100.0, the language and compiler ecosystem continue to resolve long-standing foreign function interface limitations while modernizing target tiering. As announced in Announcing Rust 1.99.0, the toolchain brings stabilized definitions of C-ABI variadic functions in Rust, precise APIs to calculate layout information from raw pointers without creating intermediate references, and updated guidance regarding memory deallocation following pointer leaks. Simultaneously, compiler and security updates address CI cache safety and target maintenance overhead across legacy architectures.
TL;DR
Rust 1.99.0 stabilizes extern "C" and extern "C-unwind" variadics, raw pointer layout inspection via functions like Layout::for_value_raw, and introduces APIs including Box::into_non_null and Vec::into_parts. Rust documentation now recommends against round-trip unleaking after calling Box::leak. Meanwhile, 32-bit Windows host toolchains face demotion in Rust 1.100.0, and Miri has patched cache-exposure risks in CI workflows.
Native C-ABI Variadics: Defining Variable Argument Functions in Rust
For systems bridging Rust into POSIX runtimes, operating system kernels, or legacy dynamic shared libraries, variadic signatures (the C ellipsis syntax ...) have historically represented an asymmetric boundary. While Rust code has long been capable of linking against and calling externally defined variadic functions such as libc::printf, authoring a variadic function directly in Rust was not supported on stable channels. Engineers previously relied on C stubs or custom assembly shims to accept variable argument lists from non-Rust callers.
Rust 1.99.0 settles this asymmetry by stabilizing the definition of C-ABI variadic functions using the "C" and "C-unwind" ABIs. Functions declared in this manner accept an arbitrary number of arguments via ... syntax. The type of the argument list within the function body is core::ffi::VaList, which maintains ABI compatibility with the C va_list type across compilation targets. To ensure safety across architectures, the types that can be read from a VaList are strictly guarded by the VaArgSafe trait.
The code below illustrates how an integer accumulator can be authored in Rust and exposed across C ABI boundaries to accept an arbitrary sequence of arguments:
use core::ffi::VaList;
/// Calculates the sum of two or more 32-bit integers passed via C variadic calling conventions.
///
/// # Safety
///
/// The caller must ensure that at least two `i32` arguments are provided in the variadic argument list.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn sum_two_or_more(mut args: ...) -> i32 {
// SAFETY: The caller guarantees that at least two valid i32 values are passed.
let first = unsafe { args.next_arg::<i32>() };
let second = unsafe { args.next_arg::<i32>() };
first + second
}
/// Demonstrates invoking the Rust-defined variadic function directly.
pub fn call_variadic_sum() -> i32 {
// SAFETY: Passing two i32 literals satisfies the safety requirements of sum_two_or_more.
unsafe { sum_two_or_more(10_i32, 25_i32) }
}
In addition to standard C calling conventions, Rust 1.99.0 stabilizes support for defining naked variadic functions with non-"C" ABIs. These low-level naked functions must be written using inline assembly, allowing kernel developers and systems programmers to handle raw register and stack parameters without interference from compiler-generated function prologues.
Layout Introspection from Raw Pointers
Custom memory allocators, runtime type systems, and serialization drivers often manipulate raw pointers pointing to uninitialized, partially initialized, or dynamically sized data. Historically, inspecting the memory footprint of dynamically sized types (such as slices or trait objects) relied on functions like core::mem::size_of_val, which require a shared reference (&T). However, constructing a shared reference requires that the underlying target is aligned, dereferenceable, non-null, and complies with memory validity requirements.
Attempting to construct an intermediate reference purely to calculate the required allocation size or alignment of an uninitialized raw buffer risks undefined behavior. Rust 1.99.0 resolves this issue by settling the safety requirements for retrieving size and alignment on raw pointers to both sized and non-sized types through three stabilized functions:
core::alloc::Layout::for_value_rawcore::mem::size_of_val_rawcore::mem::align_of_val_raw
These functions permit querying layout information directly from raw pointers, eliminating the need to materialize temporary references to potentially uninitialized target memory. The following implementation sketches how an uninitialized buffer allocator might inspect layout requirements safely:
use core::alloc::Layout;
use core::ptr::NonNull;
pub struct RawMemoryArena {
storage: NonNull<u8>,
capacity: usize,
used: usize,
}
impl RawMemoryArena {
/// Reserves uninitialized memory for a slice based on a raw slice pointer's layout metadata.
///
/// # Safety
///
/// The pointer `slice_ptr` must contain valid metadata for `[T]`. The target memory
/// does not need to be initialized or dereferenceable.
pub unsafe fn allocate_slice_slot<T>(
&mut self,
slice_ptr: *const [T],
) -> Result<*mut [T], ()> {
// Query the required memory layout directly from the raw pointer without creating &[T].
let layout = unsafe { Layout::for_value_raw(slice_ptr) };
let base_addr = self.storage.as_ptr() as usize;
let unaligned_addr = base_addr + self.used;
let align_mask = layout.align() - 1;
let aligned_addr = (unaligned_addr + align_mask) & !align_mask;
let padding = aligned_addr - unaligned_addr;
let total_bytes = padding + layout.size();
if self.used + total_bytes > self.capacity {
return Err(());
}
self.used += total_bytes;
let destination_ptr = aligned_addr as *mut T;
let element_count = slice_ptr.len();
Ok(core::ptr::slice_from_raw_parts_mut(destination_ptr, element_count))
}
}
Recommendations Against Round-Trip Unleaking After Box::leak
In previous Rust versions, a widespread pattern for working with unmanaged code or static contexts involved round-trip unleaking: calling Box::leak to obtain a &'static mut T reference, passing the pointer through external APIs, and later reclaiming the allocation by reconstituting the Box with Box::from_raw to deallocate it.
While language semantics remain unchanged in Rust 1.99.0, the standard library documentation for Box::leak has been formally updated to recommend against patterns that later deallocate that memory. This guidance was established because such round-trip patterns have problematic interactions with current and future compiler optimizations, and are especially problematic in light of the upcoming stabilization of custom allocators. The updated guidance similarly applies to other leak functions across the standard library.
Instead of relying on Box::leak when future deallocation is intended, developers should utilize explicit pointer conversion functions. Rust 1.99.0 stabilizes Box::into_non_null and Box::from_non_null as preferred primitives alongside Box::into_raw:
use std::ptr::NonNull;
pub struct DiagnosticPacket {
pub channel_id: u32,
pub payload: Vec<u8>,
}
/// Converts a boxed structure into a non-null raw pointer for external transit.
pub fn export_packet(packet: Box<DiagnosticPacket>) -> NonNull<DiagnosticPacket> {
// Box::into_non_null is preferred over Box::leak when the caller will later deallocate the memory.
Box::into_non_null(packet)
}
/// Safely reconstructs the Box to reclaim and deallocate the memory.
///
/// # Safety
///
/// The `handle` pointer must have originated from a prior call to `Box::into_non_null`
/// and must not have been previously deallocated.
pub unsafe fn destroy_packet(handle: NonNull<DiagnosticPacket>) {
// Reclaim ownership to execute Drop and clean up the heap allocation.
let packet = unsafe { Box::from_non_null(handle) };
drop(packet);
}
Alongside these pointer utilities, the 1.99.0 release expands standard library capabilities with an assortment of foundational APIs, including:
Vec::into_partsandVec::from_partsfor direct vector buffer destructuring and reconstructionIntoIteratorimplementations forBox<[T; N]>,&Box<[T; N]>, and&mut Box<[T; N]>VecDeque::retain_backString::from_utf8_lossy_ownedandstring::FromUtf8Error::into_utf8_lossyFusedIteratorforStepBy<I>std::fs::set_timesandstd::fs::set_times_nofollow
Toolchain Demotions: 32-Bit Windows Shift to Target-Only Support
Maintaining compiler host toolchains on legacy architectures presents growing challenges for upstream maintenance. As detailed in the compiler team announcement Demoting i686 Windows targets to std-only, the Rust compiler team will implement major target tier changes for 32-bit Windows starting with the release of Rust 1.100.0:
| Target Triple | Previous Status | Status in Rust 1.100.0 | Continuous Integration Testing |
|---|---|---|---|
i686-pc-windows-msvc |
Tier 1 with host tools | Tier 1 without host tools | Maintained on CI |
i686-pc-windows-gnu |
Tier 2 with host tools | Tier 2 without host tools | No host tools |
After Rust 1.100.0, installing compiler toolchains on 32-bit Windows hosts will no longer be possible. Desktop and server 32-bit x86 CPUs have not been sold for over 15 years, and general 32-bit Windows support ended in October 2025. Consequently, the environments these host targets serve rarely exist in practice, and available machines generally lack sufficient resources for modern development workloads.
Even when compiled on modern x86_64 hardware, building 32-bit Windows toolchains has triggered critical compiler instability. Compiler binaries built with the i686 MSVC target have crashed, and the GNU C++ toolchain has failed due to out-of-memory errors during LLVM builds. To balance stability and resource demands, cross-compiling from supported host environments, such as 64-bit Windows, is now required to build 32-bit Windows binaries. Builds of the prebuilt standard library will continue to be distributed, and i686-pc-windows-msvc remains subject to CI testing. Other 32-bit architectures are unaffected by this adjustment. Detailed rationale and community feedback are documented under RFC 3999 for the MSVC demotion and MCP 1020 for the GNU demotion.
Security Advisory: Remediating Miri Target Cache Leakage in CI
Maintaining a secure development lifecycle requires auditing CI workflows to prevent artifact caches from exposing sensitive operational data. On September 21, 2026, the Rust Security Response Team disclosed an advisory regarding GitHub Actions leaking secrets when Miri output is cached.
When executing cargo miri, Miri must retain build-relevant environment variables across invocations. The historical implementation achieved this by persisting all current environment variables directly into the target/ directory. In continuous integration pipelines, developers frequently cache the target/ directory via actions/cache or swatinem/rust-cache to accelerate build times. In GitHub Actions, workflow runs on the default branch write to cache volumes that pull request workflows are permitted to read.
This design created an exposure vector when cargo miri ran in CI steps that had access to secrets. Any contributor able to submit a pull request could read the cached target/ directory, extract persisted secrets, and potentially obscure the intrusion by pushing subsequent commits to overwrite commit history. The Security Response Team outlined the conditions required for this vulnerability:
- The CI pipeline runs
cargo miri. - The step executing
cargo mirihas access to secrets passed directly via step environment variables, workflow-levelenvdefinitions, or preceding steps that persist values into the environment. - The workflow caches the
target/directory (such as withactions/cacheorswatinem/rust-cache). - The cache is readable by pull requests.
To eliminate this threat, Miri's behavior was patched in the nightly release dated 2026-09-22. The patch ensures that Miri preserves only CARGO_* environment variables (explicitly excluding authentication tokens like CARGO_*_TOKEN) and OUT_DIR. Infrastructure teams operating earlier releases should isolate Miri jobs from sensitive environment variables, disable caching for Miri execution steps, or clear existing CI caches and rotate potentially exposed secrets.
Warning
Neither Cargo, Miri, nor Rust guarantees that arbitrary environment variables are safe from being copied into target/. Build scripts and compilation artifacts should never be granted access to ambient secrets in jobs configured to write to shared or public CI caches.
Strengthening Ecosystem Maintenance: Cargo Maintainer in Residence
Sustaining core tooling and keeping pace with community feature requests requires sustained engineering resources. As announced by the Rust Funding team, Scott Schafer was appointed as a full-time Maintainer in Residence for the Cargo team, funded through contributions from the Rust Foundation Maintainers Fund, the Rust Leadership Council, and AWS.
Cargo sits at the center of the developer workflow and is deeply involved across numerous Rust project goals. In past years, the Cargo team instituted a feature freeze to address internal technical debt, refactor subsystems, and establish sustainable review processes. Schafer, who joined the Cargo team three years prior and leads the Rust Docker team, previously authored Cargo's workspace inheritance feature and led the migration of compiler diagnostics to the annotate-snippets crate completed in Rust 1.93.0. This shared diagnostic rendering interface unblocked the development of Cargo's linting system, which has been stabilized to ship in Rust 1.100.0.
Reflecting on the appointment, Schafer stated:
"I am incredibly excited to work on Cargo full-time! There have been so many things that I wish I could've worked on over the years, that I will now be able to get to. I hope that my efforts will bring Cargo into a more maintainable state."
Key takeaways
- Rust 1.99.0 enables native definition of C-ABI variadics using
...andcore::ffi::VaList. - Raw pointer size and alignment queries are stabilized via
Layout::for_value_raw, avoiding invalid shared reference creation. - Round-trip unleaking via
Box::leakis discouraged; teams should adoptBox::into_non_nullandBox::from_non_null. - Rust 1.100.0 demotes
i686-pc-windows-msvcandi686-pc-windows-gnuto targets without host tools, necessitating cross-compilation from 64-bit systems. - Miri workflows in CI must be reviewed to ensure environment secrets are not written to shared caches inside
target/.
Sources
- Demoting i686 Windows targets to std-only | Rust Blog blog.rust-lang.org · Oct 2, 2026
- Announcing Rust 1.99.0 | Rust Blog blog.rust-lang.org · Oct 1, 2026
- Announcing a Maintainer in Residence: Scott Schafer for the Cargo team | Rust Blog blog.rust-lang.org · Sep 22, 2026
- GitHub Actions leaking secrets when Miri output is cached | Rust Blog blog.rust-lang.org · Sep 21, 2026
