January 30, 2020·5 min read·engineeringrusttraitscoherence

Rust 1.41 permits more conversions without abandoning coherence

Relaxed orphan rules permit useful foreign-trait implementations involving local types. The change distinguishes covered parameters from blanket implementations that could conflict downstream.

A library should be able to convert its own generic collection into a standard vector without inventing another wrapper solely to satisfy an implementation rule. Rust previously rejected some natural forms of that conversion. Rust 1.41 relaxes the restriction while retaining coherence, the requirement that trait resolution has an unambiguous implementation to select.

The release announcement uses a local BetterVec<T> and the standard From trait to explain the change. Both From and Vec belong to another crate, but the conversion trait has a local type as its argument. The new rule recognizes enough locality in that relationship to allow the implementation.

I like Rust's attempt to make important semantic constraints explicit. This change is interesting because it removes a restriction without dismissing the problem that motivated it. Extensibility needs a precise account of where implementations can overlap.

Locality is more precise than a slogan

The orphan rule is often summarized as requiring either the trait or the implementing type to be local. That is a useful introduction, but it does not describe every permitted implementation. Generic trait arguments also contribute to the rule. Rust 1.41 changes how type parameters in those positions affect validity.

Here is a complete small conversion using the newly permitted pattern.

struct BetterVec<T>(Vec<T>);

impl<T> From<BetterVec<T>> for Vec<T> {
    fn from(value: BetterVec<T>) -> Self {
        value.0
    }
}

fn main() {
    let values: Vec<i32> = BetterVec(vec![1, 2]).into();
    assert_eq!(values, vec![1, 2]);
}

The current crate owns BetterVec. It does not own From or Vec. The implementation connects the local wrapper to the foreign vector while retaining the same element type. Earlier restrictions rejected this generic form even though a corresponding implementation for a concrete vector element type could be allowed.

RFC 2451 describes the distinction through covered type parameters. In Vec<T>, the parameter T is covered by Vec. A bare T is uncovered. The revised rule constrains uncovered parameters before the first local type, instead of applying the older restriction to covered parameters as well.

A type does not become local merely because it contains a local parameter. The RFC explicitly distinguishes LocalType<ForeignType>, which is local, from Vec<LocalType>, which is not. Type aliases do not change locality. These details prevent a superficial substitution of names from bypassing the rules.

Coherence protects composition between crates

The purpose of coherence becomes clearer when two libraries enter the same application. If each could independently implement the same foreign trait for the same foreign type, merely depending on both could make trait selection ambiguous. Rust restricts implementations so that independently developed crates cannot introduce that conflict freely.

The rules also preserve room for a library to evolve. An upstream crate adding an implementation can collide with a downstream implementation that was previously legal. The RFC treats potentially overlapping blanket implementations as breaking changes. Relaxing one restriction does not imply that upstream authors can add every generic implementation compatibly.

The vector conversion illustrates the boundary. A broad implementation such as a conversion from arbitrary T into Vec<T> could conflict with downstream conversions involving local types. The RFC points out that such blanket additions already require a compatibility assessment. Rejecting the narrower local conversion does not make the broad upstream addition harmless.

The order of parameters still matters. An uncovered parameter before the first local type can permit overlap with an implementation from another crate. The RFC retains an ordering restriction to rule out those cases. This is why the release cannot be accurately described as allowing arbitrary foreign-trait implementations whenever a local type appears somewhere in the declaration.

Extensibility motivated the relaxation

The RFC uses Diesel to show why the old restriction affected more than collection conversions. A query representation and a database backend can be extended independently. Different databases may require different SQL syntax for an operation, while a new query node may apply across multiple backends.

An implementation relating a local backend to a generic foreign query node can be valid without threatening the uniqueness of trait selection. The previous rule nevertheless blocked a needed form. Reversing trait parameters could solve some cases but prevented other extension directions. The design problem was to support both kinds of third-party extension within a coherent implementation system.

This example also explains why a wrapper workaround can impose more than cosmetic cost. A wrapper changes the types an extension must expose and how users compose it with the original library. If the coherence model can safely permit the direct relationship, forcing a wrapper adds an abstraction unrelated to the domain.

I regard the covered-parameter distinction as useful for understanding the change, even if most application authors encounter it only through diagnostics. It explains why one generic implementation becomes legal while another still risks overlap. The rules support composition by identifying the actual source of ambiguity.

The RFC acknowledges a teaching cost. The short locality explanation becomes less complete as more safe cases become expressible. That cost is deliberate, since rejecting composable implementations solely to preserve the slogan would prevent the extensions that motivated the change.

Other release changes preserve separate contracts

Rust 1.41 also changes cargo install to update an installed binary crate when a newer release exists. Previously, --force was the available route to reinstalling, even when the installed package was already current. The new behavior makes an ordinary installation request account for whether the existing installation is outdated.

Cargo introduces a lockfile layout intended to reduce unnecessary merge conflicts. New lockfiles use the format while existing lockfiles retain their previous representation. Dependency versions and checksums remain the purpose of the file. The change concerns how those records are arranged for collaboration.

The release also documents ABI compatibility between Box<T> for sized T and a C pointer. Representation compatibility does not remove ownership requirements. The announcement requires a box to remain non-null, aligned, and suitable for deallocation through the global allocator, and cautions against substituting boxes for arbitrary pointers returned by C functions.

That distinction parallels the trait change in a useful way. A more permissive representation or implementation rule still has conditions. Rust 1.41 expands permitted composition while retaining the constraints that give a conversion, allocation, or trait implementation its defined meaning.