4

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?

Terseus
  • 2,082
  • 15
  • 22
  • Does this answer your question? [How to create a vector of boxed closures in Rust?](https://stackoverflow.com/questions/49012277/how-to-create-a-vector-of-boxed-closures-in-rust) – Chayim Friedman May 14 '22 at 21:45
  • @ChayimFriedman Not exactly, with `Vec<_>` I find reasonable that the compiler didn't inferred by default to a `dyn Trait` type as it didn't had any hint, but my understanding is that here the compiler should be capable to infer it, since it can do it without the `Result`. – Terseus May 15 '22 at 07:39
  • 1
    Explaining the internals of rustc's inference is beyond the capacity of a comment on SO, or even beyond the capacity of SO in general. In rough lines, the compiler doesn't coerce whenever you want: it only coerce at specified _coercion sites_. Unifying the types of the arms of `match` is one such site, and thus the first code works, but just in the middle of an expression is not, and thus the second code fail. If you use `as` (`as _` suffices), you insert an explicit coercion site, telling the compiler "check the need for coercion here". – Chayim Friedman May 15 '22 at 07:49
  • @ChayimFriedman Mmmm that makes sense, didn't know about type coercion in that way, I suppose trying to coerce the types in every possible place may be intractable; thank you. – Terseus May 15 '22 at 07:56

1 Answers1

2

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())
}
Netwave
  • 40,134
  • 6
  • 50
  • 93
  • Thanks a lot, it works indeed, however I want to know why exactly the compiler can't infer the type; why can it do it without the `Result` but not with it? It doesn't look hard from the user perspective, what am I missing? – Terseus May 15 '22 at 07:42
  • @Terseus, I am no expert in the compiler internals. But I believe it is due to the branching. When no nested types it is able to know the branching final type. When it is nested it first infers the one branch type, then the others before compelling to the final type. But again, I cannot say this is completely accurate. – Netwave May 15 '22 at 09:56