Search code examples
rusttrait-objects

Return Result<Box<dyn Trait>> in a match


I have a set of types that implements a given trait, I want to get a concrete type object from a string name, strangely it works when my match returns Box<dyn Trait> but doesn't when I wrap it in a Result.

Given this trait and types:

trait Shape {
    fn edges(&self) -> u8;
}

struct Triangle {}

impl Shape for Triangle {
    fn edges(&self) -> u8 {
        3
    }
}

struct Rectangle {}

impl Shape for Rectangle {
    fn edges(&self) -> u8 {
        4
    }
}

This works:

fn get_shape_edges(name: &str) -> Result<u8, &str> {
    let shape: Box<dyn Shape> = match name {
        "triangle" => Box::new(Triangle {}),
        "rectangle" => Box::new(Rectangle {}),
        _ => panic!("Bad value"),
    };
    Ok(shape.edges())
}

However this doesn't:

fn get_shape_edges(name: &str) -> Result<u8, &str> {
    let shape: Box<dyn Shape> = match name {
        "triangle" => Ok(Box::new(Triangle {})),
        "rectanble" => Ok(Box::new(Rectangle {})),
        _ => Err("bad value")
    }?;
    Ok(shape.edges())
}

The error:

error[E0308]: `match` arms have incompatible types
  --> src/main.rs:24:24
   |
22 |       let shape: Box<dyn Shape> = match name {
   |  _________________________________-
23 | |         "triangle" => Ok(Box::new(Triangle {})),
   | |                       ------------------------- this is found to be of type `Result<Box<Triangle>, _>`
24 | |         "rectanble" => Ok(Box::new(Rectangle {})),
   | |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `Triangle`, found struct `Rectangle`
25 | |         _ => Err("bad value")
26 | |     }?;
   | |_____- `match` arms have incompatible types
   |
   = note: expected type `Result<Box<Triangle>, _>`
              found enum `Result<Box<Rectangle>, _>`

This is just an example of course, in my code I want to use the latter case to handle the error.

Why the latter case doesn't work?


Solution

  • You need to cast the Box to Box<dyn Shape>, otherwise the compiler is not smart enough (still) to elide the type:

    fn get_shape_edges(name: &str) -> Result<u8, &str> {
        let shape: Box<dyn Shape> = match name {
            "triangle" => Ok(Box::new(Triangle {}) as Box<dyn Shape>),
            "rectanble" => Ok(Box::new(Rectangle {}) as Box<dyn Shape>),
            _ => Err("bad value")
        }?;
        Ok(shape.edges())
    }
    

    Playground

    Btw, since you are already building a result, you could just map the edges to the original built one:

    fn get_shape_edges(name: &str) -> Result<u8, &str> {
        let shape = match name {
            "triangle" => Ok(Box::new(Triangle {}) as Box<dyn Shape>),
            "rectanble" => Ok(Box::new(Rectangle {}) as Box<dyn Shape>),
            _ => Err("bad value")
        };
        shape.map(|s| s.edges())
    }