0% read
Skip to main content
Rust 1.91.0 Release: Variadic Functions, Platform Improvements, and Enhanced Safety Lints

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.

S
StaticBlock Editorial
10 min read

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 extern blocks
  • 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::&lt;i32&gt;(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_DIR environment 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 -> Result finds functions taking raw pointers
  • Search: *mut T finds 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:

  1. Dangling pointers: dangling_pointers_from_locals
  2. Integer casts: integer_to_ptr_transmutes
  3. 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

Found this helpful? Share it!

Related Articles

S

Written by StaticBlock Editorial

StaticBlock Editorial is a technical writer and software engineer specializing in web development, performance optimization, and developer tooling.