Match statement returning a generic type implementing a trait?

I am wondering if it is possible for a match statement to return a type which just has a guarantee of having one particular trait -- thus each arm could return a different type as long as it implements that trait, and after the match statement just the methods of that trait could be used.

Here is my code snippet trying to do something like this that does not work:

use std::fmt::{Binary, Formatter};


pub struct TTest {
    value: u32,
}

impl Binary for TTest {
    fn fmt(&self, f: &mut Formatter<_>) -> std::fmt::Result {
        Binary::fmt(&self.value, f)
    }
}

pub fn example(flag: u8) -> Result<(), ()> {

    let x = TTest { value: 12 };
    let i8_var: i8 = -128;  // 8-bit signed integer
    let i16_var: i16 = -32768;  // 16-bit signed integer
    let u8_var: u8 = 255;  // 8-bit unsigned integer
    let u16_var: u16 = 65535;  // 16-bit unsigned integer

    let data = match flag {
        1 => i8_var,
        2 => i16_var,
        3 => u8_var,
        4 => u16_var,
        5 => x,
        _ => x
    };

    let bin_rep = format!("{data:b}");
    println!("{:b}", data);
    Ok(())
}

fn main() {
    example(2);
}

note: I removed a ' before _ in this code snippet on line 9 because it was messing up the code formatting in the forum for some reason (?) but that ' is included int the playground linked here:

If I were doing this in python with duck typing it would be something like:

if x == 1:
    y = something();
else if x == 2:
    y = something_else()
else if x == 3:
    y = a_third_thing()

y.my_function()

and as long as y implements my_function(), it will work, whatever the type of each arm is

Each expression, the return value from the match in this case, must have a single type. So you can't use several different types, even if they implement the same trait. But you can use dyn Trait, since this is a single type. dyn Trait uses indirection (a vtable) to abstract over the underlying type ("erase" it).

Here's how this could be done for the Debug trait -- I used Debug instead of Binary because I'm not familiar with Binary. (Although now you have changed the post to use integers.)

The key is to declare data as &dyn Debug, so that references to each expression's type are coerced to &dyn Debug.

    let data: &dyn Debug = match flag {
        1 => &"String literal",       // Arm 1: &str
        2 => &vec![1, 2, 3],          // Arm 2: Vec<i32>
        3 => &Some("optional data"),  // Arm 3: Option<&str>
        4 => &x,
        _ => &(),                     // Arm _: Unit type
    };

playground

4 Likes

thank you so much @jumpnbrownweasel this makes sense ! and thanks for translating to the Debug example, as I just updated by example to make it work more clearly in the Binary case.

your example works just as well.

2 Likes