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 have | I need | Fix |
|---|---|---|
String | &str | &s or s.as_str() |
&str | String | s.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. String → Utf8Bytes), check if From<Source> is implemented for Target.
How to check on doc.rs:
- Go to
docs.rs/<crate>/latest/struct.<TargetType>.html - Scroll to “Trait Implementations”
- Ctrl-F for
From<— look forFrom<YourSourceType> - 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:
| Method | Import needed |
|---|---|
.next(), .map(), .filter() on streams | futures_util::StreamExt or tokio_stream::StreamExt |
.send() on split WebSocket sink | futures_util::SinkExt |
.eventsource() on byte stream | eventsource_stream::Eventsource |
.oneshot() on tower service | tower::ServiceExt |
How to find the right import:
- The compiler error usually says “method not found in
SomeType”. Start there. - If you know which crate
SomeTypecomes 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. - 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.
- 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,Mutexwhen shared mutation is needed - Clone: if the data is cheap to copy, just
.clone()it - Restructure: pass separate
&mut field1, &mut field2instead 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.
BroadcastStreamneedstokio-stream/sync,bytes_stream()needsreqwest/stream) - Crate version mismatch: two versions of the same crate = different types that look identical.
cargo tree -dshows 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
letbinding - 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
- Rust Option and Result conversions — Option/Result bridging patterns
- Rust Streams and StreamExt — the two StreamExt traits and when to use which
- Axum extractors and type destructuring — extractor type patterns