Rust 1.89 distinguishes inferred values from public contracts
Const-generic inference removes repeated values inside implementations while a new lint makes borrowed return types clearer. Cross-target doctests and ABI corrections expose assumptions at other boundaries.
Inference can remove a repeated value without making an interface vague. A function returning an array with a declared const-generic length already supplies the information needed to construct that array. Rust 1.89 lets the implementation use an underscore for the repeated length, while continuing to require a complete public signature.
Released on August 7, Rust 1.89 also adds a default warning for mismatched lifetime syntax. The pairing is useful. Information already available inside a function can be inferred, while a lifetime relationship that matters to a caller should remain visible in the signature.
I like Rust's type system when its precision helps explain an interface. Repeating a constant the compiler already knows adds little information. Hiding that a returned iterator borrows an input removes information a reader may need. These changes draw different conclusions because the information serves different purposes.
Const arguments can be inferred locally
The new inference form allows an underscore where a const-generic argument or an array-repeat length can be inferred from context. The release provides a function whose declared return length determines its repeated array length:
fn cleared<const N: usize>() -> [bool; N] {
[false; _]
}
let flags: [bool; 8] = cleared::<_>();
assert_eq!(flags, [false; 8]);
The result annotation supplies the length for the function call, and the return type supplies the length inside the function. Neither underscore introduces a runtime-sized array. The resulting array type still has a compile-time length.
The release explicitly excludes inferred const arguments in item signatures and constants' type declarations. A public return type such as [bool; _] is not an accepted replacement for [bool; N]. The caller needs an established contract against which the implementation is checked.
This boundary also explains why the feature is narrower than arbitrary compile-time reasoning. The underscore requests inference of a value from available type information. It does not provide a general solver for every arithmetic relationship an application could write between const parameters.
Lifetime elision can hide a return dependency
The new mismatched_lifetime_syntaxes lint warns by default when connected input and output lifetimes use inconsistent forms. Its motivating example takes a slice reference and returns a slice iterator whose lifetime parameter is omitted in the type path. Type checking knows the relationship, but the returned type does not visibly show that it contains a lifetime.
fn items(values: &[u8]) -> std::slice::Iter<'_, u8> {
values.iter()
}
The placeholder lifetime makes the borrow in the returned iterator visible without inventing a named lifetime that adds no distinction. The iterator remains tied to the borrowed slice under the ordinary elision rules. This is a clarification of the written contract, not a change to the iterator's ownership.
The release explains that an earlier broader lint about elided lifetimes in type paths produced too much noise. A lifetime inside a formatter argument does not always help a reader understand a function's return relationship. The new warning focuses on the inconsistency between connected input and output forms.
This is a useful approach to diagnostics. Requiring explicit syntax everywhere can make important relationships harder to distinguish from routine annotations. A focused warning asks for visibility where inference connects the two sides of a function signature.
Cross-target examples become executable evidence
Doctests now participate when running cargo test --doc --target for another target. The release notes that this can reveal failures in examples that were previously skipped. A documentation example can compile on the host while depending on assumptions that do not hold for the target the crate claims to support.
This does not make an arbitrary target executable on the host. The test environment still needs whatever target support and execution arrangement its normal cross-testing requires. The change removes an omission in which documentation tests did not participate in that selected target workflow.
Target-specific ignores remain available for examples with genuinely limited applicability. They should describe that limitation explicitly. Ignoring a failing example merely to retain a green result would remove the evidence that the example and the target contract disagree.
Integer width alone does not define an ABI
Rust 1.89 permits i128 and u128 in extern "C" definitions without the earlier improper-types warning. The release carefully relates them to C's __int128 forms where those forms exist. It does not promise compatibility with every C type that happens to contain 128 bits.
The earlier layout explanation separates size and alignment from argument passing. Two types can occupy the same number of bytes while disagreeing about storage alignment or the registers and stack slots used by a call. Both relationships matter at a foreign-function boundary.
In particular, the release warns that _BitInt(128) need not have the same ABI as __int128. Choosing the C declaration by width alone can therefore create an incorrect binding even after Rust accepts the signature without warning. The binding must match the intended target and C type.
The release also completes standards-compliant C ABI handling for wasm32-unknown-unknown, described in an earlier announcement. That is another case where the calling contract matters independently of a source-level type's familiar spelling. Code crossing a compiled boundary needs agreement from both sides.
The release also adds x86 target features and many AVX-512 intrinsics. Their availability in the compiler is separate from availability on the executing processor. A binary still needs the correct target-feature contract for the instructions it can execute. This is the same distinction between expressing an operation in source and establishing the conditions under which the compiled operation is valid. Adding an intrinsic does not make every supported deployment machine capable of running it.
Different visibility for different information
I consider these distinctions more useful than a general preference for explicitness or inference. A const value already established by the result type can be omitted locally. Returned borrows need visibility where a reader assesses lifetime constraints. Foreign types require a matching ABI even if their numeric widths look obvious.
The release improves each situation through a different mechanism. Inference handles a local relationship, a lint improves the signature's presentation, and target-aware documentation testing exposes portability assumptions. The compiler still checks against complete item contracts, including array lengths and the calling convention at foreign boundaries.