Mehdi Akiki
Rust Failure Atlas / Language and diagnostics

RFA-529 · Case file with fixtures · Case 501 of 694 · Compiler evidence

Rust Generic Conversions May Need an Intermediate Result Type

Two generic operations can leave a shared intermediate type underconstrained even when the final result is known. Name the conversion result before applying the next operation.

Reviewed
Rust
Rust 1.98.1, edition 2024
Targets
all Rust targets
Profiles
dev, release, test

Direct answer

What this Rust failure means

Why it happens
The final addition result does not uniquely determine a shared intermediate type across two independent trait parameter relationships.
First discriminating check
Name the conversion destination with a typed intermediate or Target::from/TryFrom, then apply the operator after validation and unit meaning are explicit.

I wrote large + small.into() where large is u64 and small is u32. Rust emitted E0284 because the intermediate output of into() was not uniquely determined by the surrounding generic operations.

The failing fixture looks obvious to a human expecting u64, but the trait system admits more abstract possibilities than builtin arithmetic intuition suggests.

Into chooses an output type from context

Into<T> is generic over destination T. Calling .into() without a turbofish relies on expected type information to select that destination.

At the same time, Add<Rhs> can be implemented with different right-hand types and an associated output. Knowing the final assignment is u64 does not always uniquely solve the intermediate Rhs.

The official E0284 page describes this combined ambiguity.

The repaired fixture names the intermediate

The repaired fixture writes let converted: u64 = small.into() before adding it. Now Into<u64> is selected directly and ordinary u64 addition follows.

This is often clearer than a dense fully qualified trait call. The local name also gives a place to validate, log, or inspect conversion separately.

I annotate the value whose type was genuinely ambiguous.

Final output does not always constrain every operand

Trait implementations can map several RHS types to the same addition output. Custom numeric and unit types make this common, but the solver must respect the general trait definitions even in a familiar-looking expression.

Rust avoids assuming that u64 + something -> u64 means something must be u64. That assumption is not part of the Add trait contract.

I follow the actual constraints rather than expected arithmetic convention.

From can state the destination at the call

u64::from(small) explicitly names the destination and works when From<u32> for u64 exists. This can be the cleanest repair for an infallible conversion.

u64::try_from(value)? is appropriate when narrowing or validation can fail. as has different cast semantics and should not replace trait conversion without considering truncation and domain rules.

The choice communicates failure behaviour as well as type.

Turbofish is not available in every intuitive position

Because into's destination is a trait parameter rather than a method-generic parameter in the ordinary call form, small.into::<u64>() is not the general syntax people expect. A type annotation, From, or fully qualified form selects it.

I prefer the simplest idiomatic form that remains explicit. Error experiments in a small fixture help separate method generics from trait generics.

Break complex chains at semantic boundaries

Iterator collection, parsing, conversion, and operator overloading can combine several inference variables in one line. Introducing one named intermediate often resolves the compiler error and improves failure handling.

I do not split every fluent expression by default. I split where a domain changes: raw to validated, narrow to wide, borrowed to owned, or transport to model.

The annotation then documents architecture rather than compiler appeasement.

Conversion overflow belongs in the chosen API

The fixture widens u32 to u64, so From is lossless. In the reverse direction, u64 may not fit in u32; TryFrom exposes that possibility. An as cast would follow numeric-cast rules and may truncate, which is a different contract.

I test minimum, maximum, and one out-of-range value when narrowing. The intermediate variable can hold a Result or the validated destination and gives errors a useful domain name.

Units can create intentional Add implementations

Custom types may implement Add between metres and offsets or timestamps and durations. In those APIs, several right-hand types are not merely theoretical. A vague .into() hides which unit the operation expects.

I use constructors like Duration::from_secs, explicit newtypes, or a typed local. This prevents a compiler annotation from becoming a silent unit conversion. Good type resolution and good domain modelling point in the same direction here.

My E0284 checklist

  • Which function or method is generic over its return type?
  • Which later operator is also generic over an operand?
  • Does the final output fail to determine the intermediate uniquely?
  • Can a small typed local name the conversion boundary?
  • Would Target::from or Target::try_from be clearer?
  • Is an as cast semantically safe for this domain?
  • Am I confusing method generics with trait parameters?
  • Does the repaired test cover boundary and failure values?

The core principle is that type inference needs one unique solution across all participating traits. When two generic operations leave a middle type open, I name that type at the real conversion boundary.