Rust 1.86 lets trait objects expose their supertraits
Trait-object upcasting removes conversion workarounds at library boundaries. Disjoint mutable access adds another way to express a valid relationship directly through a checked standard-library operation.
A library can declare that one trait requires another and still encounter friction when passing a trait object to a function that needs only the smaller interface. The concrete implementation satisfies both traits, but an erased object needs the correct dynamic-dispatch information for the interface the callee expects. Rust 1.86 finally makes that conversion a supported coercion.
Released on April 3, Rust 1.86 stabilizes trait-object upcasting. It also adds checked access to several disjoint mutable elements in slices and hash maps. Both changes let an API express a relationship that programmers could previously establish only through workarounds or a different arrangement of their code.
I like Rust's insistence that an abstraction justify its memory behavior. These additions improve the language's usefulness without asking callers to abandon that discipline. An upcast follows a declared trait relationship, while a disjoint-access method checks the condition required to return several mutable references.
A supertrait is already a requirement
RFC 3324 describes the motivation for trait-object upcasting. If Writer has Reader as a supertrait, an implementation of Writer must also implement Reader. Generic code can use that relationship, but code operating through dyn Writer previously could not simply pass that object where dyn Reader was required.
That limitation forced interface authors to anticipate conversions. A trait could add an as_reader method that returned another trait-object reference. Such a method encoded a relationship the trait declaration already stated, and its signature generally supported one particular pointer or borrow form.
Rust 1.86 allows the coercion directly:
trait Source {
fn length(&self) -> usize;
}
trait Document: Source {
fn title(&self) -> &str;
}
fn source(document: &dyn Document) -> &dyn Source {
document
}
The function returns a view through the supertrait without copying the underlying document. The trait declaration establishes why every document implementation supplies the required source behavior. No extra conversion method is necessary merely to repeat that requirement in executable form.
The release also supports corresponding conversions for smart pointers and raw pointers. That broader support is important because a reference-specific workaround would not automatically solve an ownership-bearing pointer case. The language now handles the coercion at the pointer-to-trait-object boundary where the relationship applies.
Dynamic metadata may need to change
An upcast is not merely permission to reinterpret a pointer's bits. Trait objects carry dynamic-dispatch metadata for their interface. The RFC explains that a supertrait's method table cannot always be treated as a simple prefix of the original table, so the conversion can require adjusting metadata.
The RFC deliberately does not stabilize a specific vtable layout. Its implementation discussion explains how upcasting can work, but a low-level library should not turn that discussion into an ABI assumption. The stable operation is the coercion, whose result has the appropriate interface metadata.
This also explains why the feature is a coercion rather than arbitrary container covariance. A Box<dyn Document> can become a Box<dyn Source> at a supported coercion point. A vector containing the former is not automatically a vector containing the latter. Each element's conversion may require work, and the enclosing collection does not acquire a new element representation by declaration alone.
Nor does upcasting permit an arbitrary combination of ordinary traits in a trait object. The declared supertrait relationship supplies a route to a particular interface. It does not make every intersection of interfaces a valid dyn type or establish relationships that were never declared.
Raw trait-object pointers need valid metadata
The release announcement calls out a consequence for unsafe code. Safe code can upcast a raw trait-object pointer, so an invalid vtable must not escape into that code. The operation can use metadata even when a caller has not explicitly dereferenced the pointed-to value.
This is a reminder that raw pointers are not unstructured integers. A raw pointer's validity requirements can include the meaning of its metadata. An abstraction manufacturing a trait object must account for the operations safe callers are allowed to perform on it, including newly supported coercions.
The announcement says the precise status of temporarily constructed invalid vtables remains unsettled and advises against creating them. That uncertainty should not be converted into permission to rely on a convenient temporary representation. An unsafe implementation needs a justified invariant for the whole value it exposes.
The Any supertrait supplies a practical use case without raw-pointer manipulation. A trait object whose trait requires Any can upcast to dyn Any and use its downcast methods. The release demonstrates this as a way to remove additional adapter methods while retaining runtime type inspection through the standard interface.
Several mutable borrows need a shared check
The slice implementation adds get_disjoint_mut. A caller supplies an array of indices or supported ranges, and the method returns mutable references only after checking that the selections are in bounds and disjoint. The result type represents failure explicitly.
let mut values = [10, 20, 30];
let [first, last] = values.get_disjoint_mut([0, 2])?;
*first += 1;
*last += 2;
This fragment belongs in a function returning an error compatible with std::slice::GetDisjointMutError. The two indices are valid and different. The method establishes their relationship together, allowing both references to exist while preserving Rust's requirement against overlapping mutable access.
Repeated calls to a single-element get_mut cannot generally express that relationship to the borrow checker. Each call borrows the collection through the same receiver. The combined operation has enough information to check the selections before creating references, so it can justify an outcome that two independent calls cannot communicate through their signatures.
The source places the unsafe construction behind the validity check. That is the useful abstraction boundary. Ordinary callers receive safe references, while the library implements the reasoning about bounds and overlap once. A caller does not need to reproduce pointer arithmetic merely to update two known-distinct entries.
Hash maps expose different failure forms
The hash-map method has the same name but a different return contract. It returns an array of optional mutable references, using None for missing keys. Overlapping keys cause a panic. A slice's result handling should therefore not be copied into a map example without checking the signature.
Missing data and conflicting access are separate cases. A missing key provides no mutable reference at that position. Two requests for the same stored value cannot both produce mutable references, regardless of whether the application intends to write equal data. The method prevents that overlap before exposing the references.
The unchecked variants move the proof obligation to the caller. The map documentation says overlapping keys cause undefined behavior even if the resulting references are never used. This prevents an incorrect interpretation in which a caller obtains overlapping references and assumes the program remains valid because one is discarded later.
I prefer the checked operation for ordinary code because it describes the desired relationship directly. If the algorithm needs two entries simultaneously, checking them together is a natural part of that operation. The unchecked form belongs only where the surrounding abstraction can justify its stronger preconditions.
Safe construction is more useful than scattered proofs
The release also permits safe functions marked with target_feature. Their calls remain constrained by whether the caller has the required processor feature enabled. Calling from a function with matching target requirements is safe, while an ordinary caller still needs an unsafe call backed by feature availability. The change separates the function body's safety from the hardware precondition of entering it.
Debug builds gain assertions against null pointers in certain operations that require non-nullness, including reference creation and nonzero-sized reads and writes. The announcement explicitly says those checks cannot establish soundness because they depend on debug assertions. A library must remain correct when such checks are absent. This distinction resembles the checked collection APIs only superficially. A safe checked method supplies a guarantee its callers may rely on, while a debug assertion helps diagnose an already-invalid unsafe operation.
Trait upcasting and disjoint access solve different problems, but both improve the expression of library boundaries. The trait hierarchy already states which smaller interface an object supports. A collection operation can determine which selections overlap. Moving those relationships into supported operations reduces the amount of local workaround code.
The release does not make every conversion or borrow implicit. Metadata still has validity requirements, collection coercions still follow their own rules, and checked access still has documented failure behavior. The improvement is that valid common operations now have a direct form whose contract reviewers can inspect, with the implementation responsible for preserving the underlying invariants.