liveBook · Manning

Trait object

As explained in Learn Rust in a Month of Lunches

A trait object is a dynamically typed Rust value represented through a trait interface, allowing the concrete implementing type to be hidden.

A trait object is a value whose concrete type is hidden behind a trait interface. This type erasure lets code use the trait’s methods without knowing which implementing type is stored. section 13.2.3 section 13.3

Returning different error types

A function can return Result<String, Box<dyn Error>> even when different branches create different error types. Box stores each concrete error, while the caller sees only the shared Error interface. The returned errors can still be printed through Debug and Display. section 13.2.3

Trait objects make several practical choices possible:

  • Replace an invalid Result<String, Error> return type, whose Error value has no known compile-time size, with Result<String, Box<dyn Error>>. section 13.2.3
  • Use only methods supplied by the trait; the concrete type remains hidden inside the Box. section 13.3
  • Recover a concrete type with downcast(), downcast_ref(), or downcast_mut() when the possible type is known. section 13.2.4
  • Understand dynamic dispatch as choosing the implementation at run time rather than turning generic types into concrete types during compilation. section 13.3
use std::error::Error;
use std::fmt;
 
#[derive(Debug)]
struct ErrorOne;
 
impl Error for ErrorOne {}                                          #1
 
impl fmt::Display for ErrorOne {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "You got the first error!")
    }
}
 
 
#[derive(Debug)]
struct ErrorTwo;
 
impl Error for ErrorTwo {}
 
impl fmt::Display for ErrorTwo {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "You got the second error!")
    }
}
 
fn returns_errors(input: u8) -> Result<String, Box<dyn Error>> {    #2
    match input {
        0 => Err(Box::new(ErrorOne)),                               #3
        1 => Err(Box::new(ErrorTwo)),
        _ => Ok("Looks fine to me".to_string()),                    #4
    }
 
}
 
fn main() {
 
    let vec_of_u8s = vec![0_u8, 1, 80];                             #5
 
    for number in vec_of_u8s {
        match returns_errors(number) {
            Ok(input) => println!("{}", input),
            Err(message) => println!("{}", message),
        }
    }
}

This listing demonstrates two concrete error types returned through one Box<dyn Error> interface, including successful and failing results. section 13.2.3

How Learn Rust in a Month of Lunches covers it

How deep it goes
Explains type erasure, trait-only access, compile-time sizing feedback, dynamic dispatch, and concrete-type recovery through downcasting.
Examples
Uses two custom errors, boxed Result values, parsing failures, and downcasting to MyError and RecvError.
What it assumes
Assumes traits, Error, Debug, Display, Result, and Box, while reinforcing their roles through compiler corrections and examples.
What you can do after
Build it from scratch by returning heterogeneous errors as Box<dyn Error> and downcasting them when their concrete types matter.
How it connects
Connects traits with Box, Error, type erasure, dynamic dispatch, and downcasting.

Also explained in

Compare how 4 books explain trait object →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Learn Rust in a Month of Lunches. The section links point to the parts of the book it draws on.