How to return Box<dyn MyTrait> from match arms without variable?

I have a match statement that returns a Box<dyn MyTrait> based on an enum. I'm able to assign the result to a typed variable and use it as intended. However, I would prefer stylistically to return the match result directly. It's a minor nit, but when chaining map() calls and such, it looks a little silly to have it as written here:

Playground

struct StructA;
struct StructB;

trait MyTrait {
    fn is_a(&self) -> bool;
}
impl MyTrait for StructA {
    fn is_a(&self) -> bool {
        true
    }
}
impl MyTrait for StructB {
    fn is_a(&self) -> bool {
        false
    }
}

enum MyEnum {
    A,
    B,
}

fn main() {
    let values: [MyEnum; 2] = [
        MyEnum::A,
        MyEnum::B
    ];

    values
        .iter()
        .map(|e| {
            // I would like to "cast" (sorry, that's the C programmer in me
            // speaking) the match statement's return value as Box<dyn MyTrait>
            // directly without the use of the b variable.
            let b: Box<dyn MyTrait> = match e {
                MyEnum::A => Box::new(StructA),
                MyEnum::B => Box::new(StructB),
            };
            b
        })
        .for_each(|b| println!("{}", b.is_a()))
}

If I change the map call to the following, it fails to compile because the arms have different return types:

Playground

    values
        .iter()
        // Could I maybe use something like the "as" keyword here to "cast" the
        // result?
        .map(|e| match e {
            MyEnum::A => Box::new(StructA),
            MyEnum::B => Box::new(StructB),
        })
        .for_each(|b| println!("{}", b.is_a()))
   Compiling playground v0.0.1 (/playground)
error[E0308]: `match` arms have incompatible types
  --> src/main.rs:35:26
   |
33 |           .map(|e| match e {
   |  __________________-
34 | |             MyEnum::A => Box::new(StructA),
   | |                          ----------------- this is found to be of type `Box<StructA>`
35 | |             MyEnum::B => Box::new(StructB),
   | |                          ^^^^^^^^^^^^^^^^^ expected `Box<StructA>`, found `Box<StructB>`
36 | |         })
   | |_________- `match` arms have incompatible types
   |
   = note: expected struct `Box<StructA>`
              found struct `Box<StructB>`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` due to previous error

An idea I had was to implement something like the From trait to do the conversion myself, but this use case seems simple enough that I shouldn't need to do something like that, given that the typed variable just works. Is there a simpler way to specify the return type in the match statement?

You can do exactly that, yes: just add as Box<dyn MyTrait> at the end of first branch, and it compiles - playground.

2 Likes

Well garsh, I tried a bunch of stuff like that but must've just not gotten the syntax quite right. Thanks for the help!

it's solved but I just want to add, alternatively you can annotate the closure with explicit return type, like this:

values
    .iter()
    // Could I maybe use something like the "as" keyword here to "cast" the
    // result?
    .map(|e| -> Box<dyn MyTrait> {
        match e {
            MyEnum::A => Box::new(StructA),
            MyEnum::B => Box::new(StructB),
        }
    })
    .for_each(|b| println!("{}", b.is_a()))
3 Likes

Ah, yes, thank you for this! That's actually more what I was looking for, and I kind of prefer it stylistically, though it's good to know both ways for different contexts.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.