Rust type mismatch decision tree

A practical workflow for when the Rust compiler says “expected X, found Y”.

Step 0: Read the full error

The compiler gives you Target (expected) and Source (found). Also read the help: lines — the compiler often hands you the exact fix.

Step 1: Reference mismatch?

Most common case. The types are the same, just wrapped differently in &, &mut, or owned.

I haveI needFix
String&str&s or s.as_str()
&strStrings.to_string() or s.to_owned()
Vec<u8>&[u8]&v or v.as_slice()
&[u8]Vec<u8>v.to_vec()
any string-like&Path.as_ref()

Step 2: Conversion via Into/From?

If Source and Target are different owned types (e.g. StringUtf8Bytes), check if From<Source> is implemented for Target.

How to check on doc.rs:

  1. Go to docs.rs/<crate>/latest/struct.<TargetType>.html
  2. Scroll to “Trait Implementations”
  3. Ctrl-F for From< — look for From<YourSourceType>
  4. If it exists → .into() bridges them

Into<T> is the mirror of From<T> — Rust auto-implements Into<T> for any type that has From. So you always call .into() on the source, never implement Into manually.

For numeric types: widening conversions (u32 → u64) have From. Narrowing (u64 → u32) does NOT — use .try_into()? or as u32 (lossy).

Step 3: Missing trait import?

Error looks like “method .xxx() not found for type Y” even though you know it exists.

The method is defined on a trait that isn’t in scope. Common culprits:

MethodImport needed
.next(), .map(), .filter() on streamsfutures_util::StreamExt or tokio_stream::StreamExt
.send() on split WebSocket sinkfutures_util::SinkExt
.eventsource() on byte streameventsource_stream::Eventsource
.oneshot() on tower servicetower::ServiceExt

How to find the right import:

  1. The compiler error usually says “method not found in SomeType”. Start there.
  2. If you know which crate SomeType comes from, go to its doc.rs page and look at “Methods from Deref” and trait implementations — the method might be on a trait that needs importing.
  3. If you don’t know the crate, paste the method name into a search engine with “rust” — e.g. “rust .eventsource() method”. The first result is usually the trait’s doc.rs page.
  4. The compiler sometimes also suggests: “the following trait defines an item send, perhaps you need to import it: use futures_util::SinkExt”. Read all the help lines.

rust-analyzer also helps: hover over the method in working example code to see which trait provides it.

Step 4: Type inference failure?

Error: “type annotations needed” or “cannot infer type”.

This happens with generic methods where the return type isn’t determined by context. Fix with turbofish or a type annotation:

// Turbofish on the method
"42".parse::<u32>()?
iter.collect::<Vec<_>>()
 
// Or annotate the binding
let n: u32 = "42".parse()?;

Rule: if a method is generic over its return type, Rust often can’t infer it. Turbofish or let-annotation.

Step 5: Borrow checker fight?

“Cannot borrow X as mutable more than once” — this isn’t a type error, it’s the type system telling you the design is wrong.

Solutions:

  • Split the resource: socket.split() gives two independently-owned halves
  • Interior mutability: Cell, RefCell, Mutex when shared mutation is needed
  • Clone: if the data is cheap to copy, just .clone() it
  • Restructure: pass separate &mut field1, &mut field2 instead of &mut whole_struct

Don’t fight the borrow checker with unsafe. Redesign the data flow.

Step 6: None of the above?

  • Feature flag issue: some types/methods only exist behind cargo features (e.g. BroadcastStream needs tokio-stream/sync, bytes_stream() needs reqwest/stream)
  • Crate version mismatch: two versions of the same crate = different types that look identical. cargo tree -d shows duplicate deps.
  • Force the compiler to show the real type: let x: () = my_expression; — the error message reveals the actual type.

Using rust-analyzer effectively

  • Inlay type hints show inferred types next to every let binding
  • Hover over an expression to see its type, or over a method to see which trait provides it
  • Quick-fix lightbulb (Ctrl+. / Cmd+.) suggests .into(), missing imports, adding &, adding .await

See also