Type Conversions & Coercions

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Rust 1.99.0 and the 2024 edition, checked on 2026-10-06. Rust never converts between types silently in general — let x: u64 = some_u32; is a type error — but it does apply a small set of well-defined coercions automatically, and the standard library defines conversion traits that make explicit conversions uniform. This page covers both, plus the algorithm that makes value.method() work on references and smart pointers.

From and Into

From<T> for U defines an infallible conversion from T to U. Implementing From gives you the reciprocal Into for free through a blanket impl, so implement From, accept Into:

#[derive(Debug)]
struct Isbn(String);

#[derive(Debug)]
struct Author {
    name: String,
}

impl From<&str> for Author {
    fn from(name: &str) -> Self {
        Author { name: name.to_owned() }
    }
}

impl From<Isbn> for String {
    fn from(isbn: Isbn) -> Self {
        isbn.0
    }
}

fn add_author(name: impl Into<String>) -> String {   // accepts &str, String, Box<str>, Cow<str>, ...
    let name: String = name.into();
    format!("added {name}")
}

fn main() {
    let a = Author::from("Octavia Butler");
    let b: Author = "Ursula K. Le Guin".into();
    let raw: String = Isbn("9780441013593".into()).into();
    let wide = u64::from(42u32);                        // lossless numeric conversions use From
    let as_f64 = f64::from(1.5f32);
    println!("{a:?} {b:?} {raw} {wide} {as_f64}");
    println!("{} {}", add_author("Le Guin"), add_author(String::from("Butler")));
}

From must not fail and should not lose information. It also powers error conversion in ? (see Error Handling). Lossless numeric conversions are From (u8 → u32, i16 → f32); lossy ones are not.

TryFrom and TryInto

For conversions that can fail, implement TryFrom with an Error type; TryInto comes for free. The standard library uses it for narrowing integer conversions and slice-to-array conversions:

use std::convert::TryFrom;

#[derive(Debug, Clone, Copy, PartialEq)]
struct Rating(u8);

impl TryFrom<u32> for Rating {
    type Error = String;
    fn try_from(value: u32) -> Result<Self, Self::Error> {
        match value {
            1..=5 => Ok(Rating(value as u8)),
            _ => Err(format!("rating must be 1-5, got {value}")),
        }
    }
}

fn main() {
    println!("{:?} {:?}", Rating::try_from(4), Rating::try_from(9));
    let r: Result<Rating, _> = 3u32.try_into();
    println!("{r:?}");
    println!("{:?}", u8::try_from(300i32));                       // Err(TryFromIntError(PosOverflow))
    let digits: [u8; 3] = vec![9, 7, 8].try_into().expect("exactly three");
    println!("{digits:?}");
}

AsRef, AsMut, Borrow and ToOwned

These traits express cheap reference-to-reference conversions:

Trait Use

AsRef<T> / AsMut<T>

"can be viewed as a &T`": accept many argument types cheaply, e.g. `P: AsRef<Path> accepts &str, String, PathBuf, &Path, OsString

Borrow<T>

like AsRef, plus a promise that Eq, Ord and Hash agree between the owned and borrowed forms; used by HashMap::get so a HashMap<String, V> can be queried with &str

ToOwned

the generalisation of Clone from a borrowed type to its owned form: str → String, [T] → Vec<T>, Path → PathBuf; used by Cow

use std::collections::HashMap;
use std::path::Path;

fn file_size(path: impl AsRef<Path>) -> u64 {
    std::fs::metadata(path.as_ref()).map(|m| m.len()).unwrap_or(0)
}

fn shout<S: AsRef<str>>(s: S) -> String {
    s.as_ref().to_uppercase()
}

fn main() {
    println!("{} {}", file_size("/etc/hostname"), file_size(std::path::PathBuf::from("/nope")));
    println!("{} {}", shout("dune"), shout(String::from("emma")));

    let stock: HashMap<String, u32> = HashMap::from([("Dune".to_string(), 2)]);
    println!("{:?}", stock.get("Dune"));       // Borrow<str> for String: no allocation to look up
    let owned: String = "Kindred".to_owned();  // ToOwned
    println!("{owned}");
}

FromStr

FromStr parses a value from a string slice; str::parse::<T>() calls it. Implement it for your own types alongside Display (see Strings & Text for an example).

as casts

as performs primitive casts between numeric types, bool/char and integers, and pointer types. It never fails at run time, which is exactly its danger: narrowing integer casts truncate, float-to-integer casts saturate, and sign changes reinterpret bits. Prefer From for widening and TryFrom for narrowing; use as when truncation is intended (hashing, bit manipulation) or for pointer and FFI work.

fn main() {
    let big: i64 = -1;
    println!("{}", big as u8);         // 255 -- reinterpreted bits
    println!("{}", 70_000u32 as u16);  // 4464 -- truncated
    println!("{}", 1e10_f64 as i32);   // 2147483647 -- saturated
    println!("{}", u8::try_from(big).is_err()); // true -- the checked alternative
}

Casts that are not allowed at all, such as &str as u32, are errors E0605 or E0606. The full cast table is in the Reference.

Coercions

A coercion is an implicit conversion that the compiler inserts at specific places, called coercion sites:

  • let statements with an explicit type (let s: &str = &string;);

  • static and const items;

  • function and method call arguments;

  • struct, enum-variant and array element expressions;

  • function results (the tail expression or a return).

Outside coercion sites — for example in generic arguments being inferred, or on the left of a method call — no coercion happens, which explains many confusing E0308 errors.

Kinds of coercion

Coercion From → To Example

Reference weakening

&mut T → &T

pass &mut v where &Vec<T> is expected

Raw pointer

&T → *const T, &mut T → *mut T

FFI calls

Deref coercion

&U → &T when U: Deref<Target = T> (repeatedly)

&String → &str, &Box<Vec<T>> → &[T]

Unsizing

[T; N] → [T], T → dyn Trait, inside pointers

&[u8; 4] → &[u8], Box<Book> → Box<dyn Describe>

Trait upcasting (1.86)

dyn Sub → dyn Super, inside pointers

&dyn Media → &dyn Debug

Function item → pointer

fn foo → fn(..) -> ..

storing functions in a table

Non-capturing closure → pointer

closure → fn(..) -> ..

callbacks

Never

! → any type

panic!() in a match arm

use std::fmt::Debug;
use std::rc::Rc;

fn takes_str(s: &str) -> usize {
    s.len()
}
fn takes_slice(v: &[u16]) -> usize {
    v.len()
}
fn takes_debug(d: &dyn Debug) -> String {
    format!("{d:?}")
}

fn main() {
    let owned = String::from("Dune");
    let boxed: Box<String> = Box::new(owned.clone());
    let rc: Rc<String> = Rc::new(owned.clone());
    println!("{}", takes_str(&owned));     // &String -> &str (Deref)
    println!("{}", takes_str(&boxed));     // &Box<String> -> &String -> &str (Deref twice)
    println!("{}", takes_str(&rc));        // &Rc<String> -> &String -> &str

    let years = [1965u16, 1815];
    println!("{}", takes_slice(&years));   // &[u16; 2] -> &[u16] (unsizing)

    let mut v = vec![1, 2];
    let exclusive: &mut Vec<i32> = &mut v;
    println!("{}", takes_debug(exclusive)); // &mut Vec -> &Vec (weakening) -> &dyn Debug (unsizing)
}

Deref coercion and custom Deref

Implementing Deref<Target = T> for your type lets &YourType coerce to &T and gives it all the &self methods of T through auto-deref. This is designed for smart pointers (Box, Rc, String, Vec). Using Deref on a newtype to "inherit" the inner type’s methods is a known anti-pattern (it emulates inheritance, surprises readers and leaks the inner type’s API); implement the methods you need, or AsRef, instead.

use std::ops::Deref;

/// A smart pointer that counts how often its contents are read.
struct Tracked<T> {
    value: T,
    reads: std::cell::Cell<u32>,
}

impl<T> Deref for Tracked<T> {
    type Target = T;
    fn deref(&self) -> &T {
        self.reads.set(self.reads.get() + 1);
        &self.value
    }
}

fn main() {
    let title = Tracked { value: String::from("Dune"), reads: Default::default() };
    println!("{} {}", title.len(), title.to_uppercase()); // String methods via auto-deref
    let s: &str = &title;                                  // &Tracked<String> -> &String -> &str
    println!("{s} -- read {} times", title.reads.get());
}

DerefMut does the same for &mut. Deref coercion is transitive — the compiler applies deref as many times as needed, as &Box<String> → &String → &str showed — but each step must be a Deref impl; it never invents conversions through From.

Least-upper-bound coercion

When several expressions must have one common type — the branches of an if or match, the elements of an array literal — the compiler tries to find a type that all of them coerce to. Function items, for instance, all become a function pointer, and references to different types implementing a trait can be unified by annotating the target:

use std::fmt::Display;

fn by_title() -> &'static str {
    "title"
}
fn by_year() -> &'static str {
    "year"
}

fn main() {
    let descending = true;
    let pick = if descending { by_title } else { by_year }; // two fn items -> fn() -> &'static str
    println!("{}", pick());

    let fields = [by_title, by_year];                       // array of fn pointers
    println!("{}", fields.len());

    let label: &dyn Display = if descending { &1965 } else { &"unknown" }; // annotated target
    println!("{label}");
}

Without the annotation on label, the two branches have types &i32 and &&str, and there is no least upper bound, so the compiler reports E0308 ("`if` and else have incompatible types").

Coercions and generics

Coercions do not happen when a generic type parameter is being inferred: passing &String to fn f<T: Display + ?Sized>(x: &T) infers T = String, not str. And a trait implemented for &str is not automatically available for &String in a trait-bound check (E0277), because trait matching does not apply coercions; call .as_str() or implement the trait for both. Azam (Advanced Rust, chapter 4) discusses these cases in detail.

The method-call algorithm: auto-ref and auto-deref

For receiver.method(), the compiler searches for a matching method by trying, in order, the receiver type T, then &T, then &mut T; then it dereferences (*T) and repeats; finally it tries an unsizing step (array to slice). The first type that has a method with that name (inherent methods before trait methods at each step) wins:

use std::rc::Rc;

fn main() {
    let titles: Rc<Vec<String>> = Rc::new(vec!["Dune".into(), "Emma".into()]);
    // len: Rc<Vec<String>> -> &Rc -> &mut Rc -> deref to Vec<String> -> &Vec: found Vec::len
    println!("{}", titles.len());
    // first: ... Vec<String> -> deref to [String] -> &[String]: found slice::first
    println!("{:?}", titles.first());
    // to_uppercase on the first element: &String -> deref to str -> &str: found str::to_uppercase
    println!("{}", titles[0].to_uppercase());
    // clone: Rc<Vec> has `clone` (via Clone) at the first step, so this clones the Rc, not the Vec
    let c = titles.clone();
    println!("{}", Rc::strong_count(&c)); // 2
}

The last line is why the idiom Rc::clone(&titles) is preferred: it makes clear that only the pointer is cloned. The Rustonomicon’s "The Dot Operator" chapter walks through more cases, including why (&x).clone() may return a reference.

transmute

std::mem::transmute reinterprets the bits of one type as another of the same size. It is unsafe, bypasses every check on this page, and is almost never the right tool — from_le_bytes, to_bits/from_bits, as, pointer casts and bytemuck-style safe transmutes cover nearly every use. See Unsafe Rust.

Common errors

Error Cause and fix

E0308 mismatched types

no coercion applies at that position: convert explicitly (.into(), u64::from(x), .as_str()), or annotate the target type

E0277 From<X> is not implemented for Y

implement From, or use TryFrom for fallible conversions

E0605 non-primitive cast

as only works on primitives; use From/Into

E0282 type annotations needed after .into()

into is generic over the target; annotate the binding or use Target::from(x)

Where the books are outdated

Azam (Advanced Rust, chapter 4) covers Deref coercion including custom Deref, coercion sites, reference, function item and trait-object coercions, coercions in generics, transitivity and least-upper-bound coercion. The only notable change since then is trait upcasting (1.86), a new unsizing coercion from one trait object to a supertrait object.