Mehdi Akiki
Rust Failure Atlas / Language and diagnostics

RFA-057 · Case file with fixtures · Case 29 of 694 · Compiler evidence

E0283: Why Rust Cannot Infer the Destination of `.into()`

Into is selected by both source and destination types. When later code does not constrain the destination, annotate the receiving boundary or name the target with From.

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

Direct answer

What this Rust failure means

Why it happens
`Into` leaves its destination as an inference variable, and the surrounding expression supplies no use that selects one of the available `From` implementations.
First discriminating check
Annotate the receiving local or name the destination with `Destination::from(value)` and confirm that only the missing output type was ambiguous.

.into() feels like it should know what to do from the value on its left. It only knows half of the conversion. Consider two identifier types:

struct UserId(u64);
struct OrderId(u64);

impl From<u64> for UserId {
    fn from(value: u64) -> Self { Self(value) }
}

impl From<u64> for OrderId {
    fn from(value: u64) -> Self { Self(value) }
}

fn main() {
    let id = 42_u64.into();
}

Rust 1.98.1 reports E0283:

type annotations needed
note: multiple `impl`s satisfying `_: From<u64>` found

The failing fixture records the exact output. Both conversions are valid. The program never says which result it wants.

Into has an output type to infer

The trait is conceptually shaped like this:

trait Into<T> {
    fn into(self) -> T;
}

The receiver fixes Self as u64, but T remains unknown. Rust gathers candidates from the available From<u64> implementations because implementing From<T> for U provides the corresponding Into<U> for T. Here the candidate set contains at least UserId and OrderId.

If later code required a UserId, inference could propagate that expectation back to .into(). Assigning the result to an unconstrained local provides no such information. Rust refuses to choose based on declaration order or proximity because adding another implementation could silently change behavior.

The standard library Into documentation recommends implementing From and receiving Into where appropriate. The important debugging fact is that conversion selection needs the destination type.

Put the type at the boundary where it has meaning

One repair annotates the receiver of the conversion:

let id: UserId = 42_u64.into();

Now the missing T is UserId. This reads naturally when a local variable represents a domain value used later.

Another repair names the target constructor:

let id = UserId::from(42_u64);

This is often clearer in a dense iterator chain or function argument, because the destination is visible beside the source. The repaired fixture demonstrates both forms and executes assertions under Rust 1.98.1.

Turbofish does not attach directly to ordinary into

A common attempt is:

let id = 42_u64.into::<UserId>();

into is not a generic method with its own type parameter; the parameter belongs to the trait. I can use fully qualified syntax:

let id = <u64 as Into<UserId>>::into(42);

It is precise but verbose. A local annotation or UserId::from normally communicates the same decision more clearly.

Ambiguity often appears far from custom identifier types

The same mechanism occurs with .collect(), .as_ref(), .parse(), and error conversions. An expression can remain ambiguous until a later method or operator also has multiple valid interpretations.

For iterators, I often write:

let names: Vec<_> = input.lines().map(str::trim).collect();

or:

let names = input.lines().map(str::trim).collect::<Vec<_>>();

Here collect needs its output collection type just as into needs its destination. The best annotation is normally at the point where the application knows the intended abstraction, not on every intermediate closure parameter.

More annotation is not always more clarity

After seeing E0283, it is tempting to annotate every local and generic argument. That can make code noisy and couple it to internal types. I begin at the first semantic boundary: the function return type, collection being built, domain identifier being created, or API parameter being passed.

One well-placed expected type often resolves an entire chain. If it does not, I split the chain into two named expressions and inspect each inferred boundary separately.

Why the compiler should not pick one

Both UserId and OrderId contain the same primitive representation, but they are deliberately different types. Guessing wrong could send an order identifier to a user lookup without any memory-safety violation. The newtypes exist to prevent that semantic mix-up.

E0283 is therefore doing more than requesting syntax. It is preserving the domain distinction that the program introduced. The official E0283 page uses collection and generic conversion examples; the reusable method is to inventory the plausible output types and state the intended one at the nearest meaningful boundary.

When I do that, .into() remains useful for obvious contextual conversions, while From remains available when readers benefit from seeing the destination directly.