Returning different types in a match


#1

Is there a way to return different types each arm of a match block?

I want to do

match foo {
   bar => Foo<u32> { ...},  
  baz => Foo<something_else> {...}
...
}

but this won’t work because it isn’t valid syntax. I am returning the same structure but on different types.

Is there a better way to do this? (even if i have to rewrite it no issues)

EDIT: The types i want to return all implement the same trait.

Thanks


#2

Are you thinking of using the “impl Trait” feature? If so, I’m afraid this won’t work. The RFC specifies:

If a function returns impl Trait, its body can return values of any type that implements Trait, but all return values need to be of the same type.

So if your function has multiple paths that return a value, each path must return the same type.

Edit: citation


#3

Yeah I did see the feature which i guess is in the nightly build. I am on the stable build (1.22.1) and I was trying to find a different way to implement this functionality. Guess it isn’t possible then.


#4

How can impl trait solve your problem?


#5

Not sure yet. I will have to dig in and try some code and see. Any concerns?


#6

impl Trait won’t help here. You can either return the type in a Box or create a custom enum that holds the different types (assuming you can name them).


#7

Thanks. Well, the types are a structure that implement a certain trait. So I am wondering if there is a better way to solve this. Also how does box-ing it solve the issue?


#8

Boxing allows you to return a trait object, which has its type erased - that allows you to return different underlying types across your match arms - caller just sees a trait object.


#9

Yeah just read through the docs on Trait Objects again and looks like it is the thing what I wanted. Thanks :slight_smile:


#10

Trait objects have some overhead though. You can make a cheaper implementation if you return a enum:

#[derive(Debug)]
enum MyOutput {
    Var1(Vec<i32>),
    Var2(Vec<String>),
}

fn func1(i: i32) -> MyOutput {
    match i {
        1 => MyOutput::Var1(vec![1, 2, 3]),
        _ => MyOutput::Var2(vec!["a".into(), "b".into()]),
    }
}

Playground

(Replace Var1 and Var2 with meaningful names if possible)

You can implement some traits or helper methods on MyOutput to make using the output easier.


#11

Thanks. But in this case i cant use an enum because as this grows i will have a lot of types (structs).


#12

I am thinking of rewriting the entire thing by implementing the trait on the enum directly. Is this present in the stable build? Cause when I searched online i found an RFC of September.


#13

I think trait objects are the simplest solution, depending on your use case you may have to Box or just use a reference, as in:

let a: &Debug = 
    match x {
        a => &"hi",
        b => &3
     };
println!("{:?}", a);

#14

You mean each enum variant being its own type? If so, that’s not implemented anywhere AFAIK.


#15

I mean if you have

Enum Foo {
Bar, 
Baz,
Faz
}

i was wondering if I could do:

impl SomeTrait for Foo::Bar {...}
impl SomeTrait for Foo::Baz {...}

#16

Right, you can’t do that.


#17

What would the meaning of this code be? Foo::Bar is not a type, so implementing a trait on it makes no sense. You could implement a trait on the enum itself. If payload for each enum variant implements a trait, you can implement that trait on the enum like this:

enum Foo {
  Bar(Payload1),
  Baz(Payload2),
}
// Payload1 and Payload2 implement SomeTrait

impl SomeTrait for Foo {
  fn someFunc(&self) {
    match *self {
      Foo::Bar(ref data) => data.someFunc(),
      Foo::Baz(ref data) => data.someFunc(),
    }
  }
}

#18

Yeah thanks for clarification.