Rust 1.91.0 Release: Variadic Functions, Platform Improvements, and Enhanced Safety Lints
Complete guide to Rust 1.91.0 with variadic function stabilization, Windows GNULLVM tier 2 promotion, new safety lints, and improved Apple platform support. Includes upgrade tutorial and migration examples.
Rust 1.91.0 releases October 30, 2025, bringing significant improvements to FFI (Foreign Function Interface) capabilities, platform support, and safety tooling. This release stabilizes C-style variadic functions for multiple ABIs, promotes Windows GNULLVM targets to Tier 2, and introduces stricter lints to catch common memory safety issues earlier in development.
Whether you're building systems software with cross-platform requirements, writing FFI bindings, or simply staying current with Rust's evolution, this release delivers practical improvements that make the language safer and more capable.
What's New in Rust 1.91.0
Variadic Function Support Stabilized
Rust 1.91 stabilizes declaration of C-style variadic functions for sysv64, win64, efiapi, and aapcs ABIs. This brings these ABIs in line with the C ABI, where variadic functions can be declared in extern blocks but not defined in Rust code.
What are variadic functions? Functions like C's printf that accept a variable number of arguments. Previously, declaring these in Rust FFI required workarounds or unstable features.
Example: Declaring C Variadic Functions
// Before Rust 1.91: Required nightly features or workarounds
// After Rust 1.91: Works on stable
extern "C" {
// Standard C library variadic functions now work directly
fn printf(format: *const i8, ...) -> i32;
fn sprintf(s: *const i8, format: *const i8, ...) -> i32;
fn fprintf(stream: *mut libc::FILE, format: *const i8, ...) -> i32;
}
fn main() {
unsafe {
// You can now call these directly on stable Rust
let msg = b"Hello %s, number: %d\n\0";
printf(msg.as_ptr() as *const i8,
b"world\0".as_ptr() as *const i8,
42);
}
}
Why this matters:
- FFI simplification: No more complex workarounds for variadic C functions
- Better interop: Libraries wrapping C APIs (logging, formatting, etc.) can use cleaner interfaces
- Standards compliance: Aligns Rust's FFI capabilities with actual C ABI requirements
Limitations:
- You can declare variadic functions in
externblocks - You cannot define variadic functions in Rust (this remains unstable)
- This is intentional—variadic function definitions have soundness concerns
Windows GNULLVM Targets Promoted to Tier 2
The aarch64-pc-windows-gnullvm and x86_64-pc-windows-gnullvm targets have been promoted to Tier 2 with host tools. This provides official support for building Windows applications using the GNU toolchain without MSVCRT dependencies.
What is GNULLVM?
- Uses GNU ABI and tooling (like MinGW-w64)
- Links against UCRT (Universal C Runtime) instead of MSVCRT
- Provides better compatibility with modern Windows
- Avoids Visual Studio dependencies
What Tier 2 means:
- ✅ Official Rust CI testing
- ✅ Guaranteed to build
- ✅ Host tools included (rustc, cargo)
- ⏳ llvm-tools and MSI installers coming in future releases
Use cases:
- Cross-compiling Windows binaries from Linux
- Building Windows apps without Visual Studio
- Creating lightweight Windows distributions
- Open-source projects preferring GNU toolchains
Installing the target:
# For x86_64 Windows (64-bit Intel/AMD)
rustup target add x86_64-pc-windows-gnullvm
For ARM64 Windows (ARM-based Windows devices)
rustup target add aarch64-pc-windows-gnullvm
Building for Windows GNULLVM:
# From Linux, macOS, or Windows
cargo build --target x86_64-pc-windows-gnullvm --release
The resulting binary will run on Windows without requiring MSVC runtime
Enhanced Safety Lints
Rust 1.91 introduces and upgrades several lints to catch unsafe patterns earlier:
1. Dangling Pointers from Locals (dangling_pointers_from_locals)
New warn-by-default lint that catches a common source of undefined behavior: creating pointers to local variables that outlive their scope.
Example of what this lint catches:
fn returns_dangling_pointer() -> *const i32 {
let local_value = 42;
// WARNING: This pointer will be invalid after function returns!
&local_value as *const i32 // ⚠️ Lint triggers here
}
fn main() {
let ptr = returns_dangling_pointer();
// Dereferencing ptr here is undefined behavior
// The memory it points to has been deallocated
}
The lint warns you with:
warning: `local_value` is borrowed locally and does not live long enough
How to fix it:
// Option 1: Return by value (preferred)
fn returns_value() -> i32 {
let local_value = 42;
local_value // Moves or copies the value
}
// Option 2: Use heap allocation if you need a pointer
fn returns_box() -> Box<i32> {
Box::new(42) // Allocates on heap, lives beyond function
}
// Option 3: Use references with proper lifetimes
fn borrows_value(value: &i32) -> &i32 {
value // Lifetime is tied to the input
}
2. Integer-to-Pointer Transmutes (integer_to_ptr_transmutes)
New warn-by-default lint against transmuting integers directly to pointers, which is almost always incorrect.
What's wrong with this:
use std::mem::transmute;
fn bad_pointer_cast() {
let address: usize = 0x1000;
// ⚠️ Warning: This is likely undefined behavior
let ptr: *const i32 = unsafe { transmute(address) };
}
Why this is dangerous:
- Provenance issues (pointer validity tracking)
- Strict aliasing violations
- Platform-specific assumptions
- Breaks optimization assumptions
The correct approach:
fn correct_pointer_cast() {
let address: usize = 0x1000;
// Use with_addr for provenance-preserving cast
let ptr = std::ptr::null::<i32>().with_addr(address);
// Or use from_exposed_addr if you really need it
let ptr = std::ptr::from_exposed_addr::<i32>(address);
}
3. Semicolon in Macro Expressions (Now Deny-by-Default)
The semicolon_in_expressions_from_macros lint has been upgraded from warn to deny. This catches macros that expand to expressions ending with semicolons, which can cause confusing behavior.
Example of problematic macro:
macro_rules! bad_macro {
($e:expr) => {
$e; // ❌ Semicolon here causes issues
};
}
fn main() {
// This will now fail to compile
let value = bad_macro!(42); // ERROR: expected expression, found ;
}
How to fix your macros:
macro_rules! good_macro {
($e:expr) => {
$e // ✅ No semicolon - returns the expression value
};
}
fn main() {
let value = good_macro!(42); // Works correctly
}
Apple Platform Improvements
Significant improvements for iOS, macOS, and other Apple platform development:
1. SDK Root Handling
Rust now automatically passes the SDK root when linking with cc, using the SDKROOT environment variable. This fixes longstanding linking issues when running rustc inside Xcode.
Before Rust 1.91:
# Manual workaround required
export SDKROOT=$(xcrun --show-sdk-path)
cargo build
After Rust 1.91:
# Just works, even inside Xcode
cargo build
2. New Target Environment Configurations
Added target_env = "macabi" and target_env = "sim" as replacements for the target_abi cfgs. This provides better clarity when targeting:
- macabi: Mac Catalyst (iOS apps running on macOS)
- sim: iOS/tvOS/watchOS Simulators
Using the new configurations:
#[cfg(target_env = "macabi")]
fn mac_catalyst_specific() {
// Code specific to Mac Catalyst environment
}
#[cfg(target_env = "sim")]
fn simulator_specific() {
// Code for iOS/tvOS/watchOS simulators
}
#[cfg(all(target_os = "ios", not(target_env = "sim")))]
fn physical_device_only() {
// Code that only runs on actual iOS devices
}
LoongArch32 Inline Assembly Support
Rust 1.91 stabilizes inline assembly for the LoongArch32 architecture. LoongArch is a RISC instruction set developed by Loongson Technology, used primarily in Chinese domestic processors.
Basic inline assembly example:
#[cfg(target_arch = "loongarch32")]
unsafe fn get_cycle_counter() -> u32 {
let count: u32;
core::arch::asm!(
"rdtimel.w {0}, $zero", // Read cycle counter
out(reg) count,
);
count
}
Significance:
- Enables systems programming on LoongArch hardware
- Supports Chinese domestic processor ecosystem
- Paves way for Rust adoption in regional markets
New Target Features: SSE4a and TBM
Rust 1.91 stabilizes the sse4a and tbm target features for x86/x86_64:
SSE4a (Streaming SIMD Extensions 4a):
- AMD-specific SIMD extension
- Improves certain multimedia operations
- Available on Phenom processors and newer
TBM (Trailing Bit Manipulation):
- AMD Bulldozer family instruction set
- Efficient bit manipulation operations
- Used in cryptography and compression
Enabling these features:
// In Cargo.toml
[target.'cfg(target_arch = "x86_64")'.dependencies]
# ...
// Or via rustc flags
RUSTFLAGS="-C target-feature=+sse4a,+tbm"
Checking feature availability at runtime:
#[cfg(target_arch = "x86_64")]
fn check_cpu_features() {
if is_x86_feature_detected!("sse4a") {
println!("SSE4a available");
}
if is_x86_feature_detected!("tbm") {
println!("TBM available");
}
}
Cargo Improvements
Stable build.build-dir Configuration
Cargo 1.91 stabilizes build.build-dir, allowing you to configure where intermediate build artifacts are stored instead of the default target/ directory.
Why customize build directory?
- Network drives: Avoid slow builds on network-mounted
target/ - Disk management: Place artifacts on faster SSD while source is on HDD
- CI optimization: Use ephemeral storage for builds
- Multi-project setups: Share artifact cache across projects
Configuring in .cargo/config.toml:
[build]
build-dir = "/tmp/rust-builds" # Use fast temporary storage
Or relative to workspace root
build-dir = "../shared-target"
Per-workspace override:
# In workspace root .cargo/config.toml
[build]
build-dir = "/mnt/fast-ssd/builds"
Behavior:
- Respects
CARGO_BUILD_TARGET_DIRenvironment variable - Works with all cargo commands (
build,test,check, etc.) - Maintains separate directories for different profiles (debug/release)
Host Triple Literal in --target
Cargo's --target flag and build.target configuration now accept the literal string "host-tuple", which resolves to your current platform's target triple at runtime.
Use case example:
# .cargo/config.toml
Force cargo to always cross-compile, even when target matches host
[build]
target = "host-tuple"
This enables better error detection for cross-platform code
Command-line usage:
# Explicitly build for your current platform
cargo build --target host-tuple
Useful in scripts that need to work across different platforms
TARGET_TRIPLE="host-tuple" cargo build --target "$TARGET_TRIPLE"
Rustdoc Enhancements
Raw Pointer Support in Type Search
Rustdoc's type-based search now properly handles raw pointers, matching the behavior of references.
What this means:
Before Rust 1.91:
- Searching for
*const u8 ->wouldn't work correctly - Functions with raw pointers showed incomplete signatures
- Had to search by function name instead of type signature
After Rust 1.91:
- Search:
*const u8 -> Resultfinds functions taking raw pointers - Search:
*mut Tfinds all functions with mutable raw pointers - Signatures display correctly:
fn foo(ptr: *const u8) -> usize
Example searches that now work:
*const c_char -> String // Find C string converters
*mut u8 -> Result // Find functions writing to buffers
&[u8] -> *const u8 // Find slice-to-pointer conversions
Practical impact:
- Easier to discover unsafe FFI functions
- Better documentation navigation for systems code
- Improved discoverability of pointer manipulation utilities
Standard Library Changes
Thread Stack Size Error Handling
Previously, std::thread::Builder::spawn() would panic if setting the thread stack size failed. Rust 1.91 now returns an error instead, allowing proper error handling.
Old behavior (panic):
use std::thread;
fn main() {
// Panicked if stack size couldn't be set
let handle = thread::Builder::new()
.stack_size(1_000_000_000_000) // Unreasonably large
.spawn(|| {
println!("This might never run");
})
.unwrap();
}
New behavior (returns error):
use std::thread;
fn main() {
match thread::Builder::new()
.stack_size(1_000_000_000_000)
.spawn(|| {
println!("Thread work");
}) {
Ok(handle) => {
handle.join().unwrap();
}
Err(e) => {
eprintln!("Failed to create thread: {}", e);
// Can handle error gracefully instead of panicking
}
}
}
Why this matters:
- More robust error handling in concurrent code
- Allows fallback strategies (e.g., reduce stack size and retry)
- Better compatibility with resource-constrained environments
- Follows Rust's philosophy of errors as values
Compatibility Notes and Breaking Changes
/usr/local/lib No Longer Linked Automatically
Libraries in /usr/local/lib may no longer be linked automatically on Unix systems. This change improves build reproducibility and security.
If your crate depends on libraries in /usr/local/lib:
// build.rs
fn main() {
// Explicitly declare the library search path
println!("cargo::rustc-link-search=/usr/local/lib");
println!("cargo::rustc-link-lib=mylib");
}
Affected platforms:
- macOS (Homebrew installs)
- FreeBSD
- Some Linux distributions
Why this changed:
- Improves build determinism
- Prevents accidental linking of system libraries
- Aligns with Cargo's explicit dependency model
Macro Semicolon Lint Now Deny-by-Default
As mentioned earlier, semicolon_in_expressions_from_macros is now deny instead of warn. This will cause compilation errors in code that previously just showed warnings.
Fixing your macros:
// Old macro (now causes error)
macro_rules! old {
($e:expr) => { $e; }; // ❌ Error
}
// Fixed macro
macro_rules! fixed {
($e:expr) => { $e }; // ✅ OK
}
Future Compatibility: Temporary Lifetime Warning
Rust 1.91 adds a future compatibility lint warning about temporary lifetimes shortening in Rust 1.92. This warns about code that will change behavior in the next release.
Code that will warn:
let x = &temp_function().field;
// Warning: The temporary returned by temp_function()
// currently lives until end of statement, but will
// change to only live for this expression in 1.92
Monitor your code: Run cargo build and watch for these warnings to prepare for Rust 1.92.
How to Upgrade to Rust 1.91
For Rustup Users (Recommended)
Update rustup itself:
rustup self update
Update Rust toolchain:
rustup update stable
Verify the installation:
rustc --version
# Should output: rustc 1.91.0 (abc123... 2025-10-30)
cargo --version
Should output: cargo 1.91.0 (xyz789... 2025-10-28)
Installing Specific Targets
If you need the new Windows GNULLVM targets:
rustup target add x86_64-pc-windows-gnullvm
rustup target add aarch64-pc-windows-gnullvm
List all installed targets:
rustup target list --installed
For System Package Managers
Arch Linux:
sudo pacman -Syu rust
Fedora:
sudo dnf upgrade rust cargo
Homebrew (macOS):
brew upgrade rust
Note: Distribution packages may lag behind the official release by a few days or weeks.
Updating Your Projects
Check for New Lints
After upgrading, run a full check on your codebase:
# Check for warnings with new lints
cargo check --all-targets
Run clippy for additional insights
cargo clippy --all-targets -- -W clippy::all
Check across all configurations
cargo check --all-features --all-targets
Address New Warnings
Focus on these new lints:
- Dangling pointers:
dangling_pointers_from_locals - Integer casts:
integer_to_ptr_transmutes - Macro semicolons:
semicolon_in_expressions_from_macros(now deny)
Test Thoroughly
# Run your test suite
cargo test --all-features
Run tests with release optimizations
cargo test --release
Check for undefined behavior with Miri (if applicable)
cargo +nightly miri test
When to Adopt Rust 1.91
Adopt immediately if:
- ✅ You're writing FFI code that needs variadic functions
- ✅ You're targeting Windows with GNU toolchain
- ✅ You're building for Apple platforms and hit SDK linking issues
- ✅ You want stricter compile-time safety checks
Wait a few weeks if:
- ⏸️ You have complex macro-heavy codebases (check for semicolon issues first)
- ⏸️ You depend on unstable features that might have changed
- ⏸️ You need time to address new lint warnings across large codebase
Standard recommendation: Update within 1-2 weeks after testing on a staging environment or local development setup.
Looking Ahead
What's Coming in Rust 1.92
Based on the future compatibility lint, Rust 1.92 will include:
- Changes to temporary lifetime rules
- Likely more ergonomic improvements
- Continued work on const generics and GATs
Rust's Release Cadence
Rust follows a strict 6-week release cycle:
- Rust 1.91.0: October 30, 2025 (this release)
- Rust 1.92.0: Expected December 11, 2025
- Rust 1.93.0: Expected January 22, 2026
Every release is thoroughly tested through nightly → beta → stable pipeline, ensuring reliability.
Conclusion
Rust 1.91.0 delivers meaningful improvements across FFI, platform support, and safety tooling. The stabilization of variadic function declarations removes a significant FFI pain point, while the Windows GNULLVM tier 2 promotion expands Rust's reach in cross-platform development.
The new safety lints—particularly dangling_pointers_from_locals and integer_to_ptr_transmutes—catch real undefined behavior that previously required careful manual auditing. Upgrading the macro semicolon lint to deny-by-default might cause some short-term friction, but it prevents subtle bugs and improves code quality long-term.
For Apple platform developers, the automatic SDK root handling eliminates a persistent source of build failures. For systems programmers targeting specialized architectures, LoongArch32 inline assembly support expands hardware compatibility.
The upgrade process is straightforward via rustup, and most codebases will require minimal changes beyond addressing new lint warnings. As always, Rust's commitment to stability means your code written for 1.90 will continue to work on 1.91.
Update soon to benefit from these improvements, and watch for the temporary lifetime warnings that preview changes coming in Rust 1.92.
Additional Resources
Related Articles
GraphQL API Design - Production Architecture and Best Practices for Scalable Systems
Master GraphQL API design covering schema design principles, resolver optimization, N+1 query prevention with DataLoader, authentication and authorization patterns, caching strategies, error handling, and production deployment for high-performance GraphQL systems.
Testing Strategies - Unit, Integration, and E2E Testing Best Practices for Production Quality
Comprehensive guide to testing strategies covering unit tests, integration tests, end-to-end testing, test-driven development, mocking patterns, testing pyramid, and production testing practices for reliable software delivery.
Monitoring and Observability - Production Systems Performance and Debugging at Scale
Master monitoring and observability covering metrics collection with Prometheus, distributed tracing with OpenTelemetry, log aggregation, alerting strategies, SLOs/SLIs, and production debugging techniques for reliable systems.
Written by StaticBlock Editorial
StaticBlock Editorial is a technical writer and software engineer specializing in web development, performance optimization, and developer tooling.