AL+

Language: RO (română) EN (English)

Email us: paul@alplustech.com

Looking back at the week of 2–8 April 2025

Trait upcasting can remove adapters without erasing interface design

Rust 1.86 trait upcasting removes conversion boilerplate. Keep public traits narrow, justify inheritance and preserve the compiler requirements of consumers.

Citiți articolul în română

Rust trait upcasting makes it easier to pass an object through a smaller interface. The design benefit is fewer conversion adapters around an existing relationship between traits. Keep that relationship justified by the domain: adding a broad parent trait simply to make conversion convenient can enlarge the public contract that every implementation must satisfy.

Looking back from November 2026, Rust 1.86.0, released on 3 April 2025, stabilized trait-object upcasting. The earlier RFC 3324 record, merged on 10 December 2022, documents the design proposal. Acceptance of that proposal and availability in stable Rust are different milestones, separated here by more than two years.

Use the relationship the traits already express

A trait describes behavior that an implementation provides. A trait object lets code use that behavior without naming the concrete implementation. When a more specific trait declares another trait as a supertrait, an implementation of the specific trait must also meet that supertrait’s requirements.

The release allows a reference to an object of the specific trait to become a reference to an object of its supertrait. It also describes corresponding conversions for other pointer forms. Earlier designs could add an explicit conversion method, but such a method introduced extra interface machinery and was tied to a particular reference or pointer form.

An illustrative document system might have a narrow trait for reading a document and a more specific trait for editing one, with reading as a genuine prerequisite. A function that only produces a preview could accept the reading interface. If it receives an editing object, upcasting can express that narrower view without a custom conversion method.

This is a proposed design example, not an account of an AL+ implementation. The useful question is whether every editor in that domain really must provide the same reading behavior. If some editors operate remotely or require a separate retrieval step, inheritance might misdescribe the actual responsibility.

A narrow parameter helps the reader

A function that accepts the reading interface tells its caller which behavior it needs. That can make the code easier to understand than accepting the complete editor interface merely because the caller happens to have an editor. The smaller requirement also leaves room for implementations that support reading without supporting edits.

Upcasting lowers the mechanical cost of keeping that parameter narrow. It does not choose the correct boundary. A function might still hide unrelated requirements in global state, or reach back to a concrete type through another mechanism. Review the operation’s actual dependencies rather than assuming the smaller type name proves a clean design.

The distinction matters to a business maintaining multiple integrations. A public trait is a promise made to implementers as well as callers. A requirement added for one integration can create work for others. Keeping the shared behavior small can reduce that coupling, although this article provides no measured maintenance saving.

Choose supertraits for obligations that genuinely travel together. Avoid a hierarchy built around the shape of one current implementation. Sharing a helper function or composing separate dependencies may express the relationship more accurately when one behavior is optional. Upcasting is useful where a valid hierarchy already exists; it is a weak reason to invent one.

Conversion is not arbitrary type recovery

Upcasting follows a declared supertrait relationship. It should not be described as a general conversion between unrelated interfaces, or as a way to combine arbitrary trait objects into one new interface. The RFC explicitly limits the proposal in that respect. A missing relationship still needs a deliberate design.

It also differs from recovering a concrete type. A narrower trait view keeps the concrete implementation abstract. If downstream code immediately needs to know which concrete type it received, ask whether the abstraction supplies the operation that code actually needs. Adding type inspection everywhere can recreate the coupling the trait was meant to avoid.

This does not ban type recovery where it is appropriate. The release discusses a useful connection with Any. The point is to keep the purpose explicit: selecting an implementation-specific facility is a different requirement from passing a smaller set of behavior to a general consumer.

Public libraries need a compiler decision

Replacing an old conversion method with stable upcasting can make a library cleaner, but its consumers need a compiler that supports the feature. Record the minimum supported Rust version before making that change. The fact that November 2026 tooling is newer does not establish what every downstream build can use.

Existing public conversion methods also have callers. Removing one during a cleanup may be an API change even if the replacement is straightforward. A migration can retain the method temporarily, document the preferred conversion and remove it through the library’s normal compatibility policy. That is a proposed option, not a universal deprecation schedule.

The strongest counterargument is that explicit adapters may already be a clearer boundary for a library with old compiler support or unrelated interfaces. Keeping them can avoid imposing inheritance or raising the minimum compiler for little practical benefit. Boilerplate is a cost, but it is not automatically the largest cost.

A local application with controlled tooling can make a different tradeoff from a widely consumed crate. Review who implements the traits, who calls the adapters and which compatibility promises exist. No Rust build or migration was performed for this article; its recommendation concerns how to choose the change, not a demonstrated local simplification.

For custom software development, use the feature to make required behavior visible at function boundaries. Retain the narrow public trait, justify each supertrait and document the compiler requirement. The best result is code with fewer adapters and a contract that remains small enough for the next implementation to meet honestly.