Returning different types in match arms

Is something like below is possible in rust (returning value of different types in match arms)

fn main() {
   
    let x = 1;
    
    let z = match x{
        1 => 0, //return int here
        2 => "hello", //return string here
    };
    println!("{:?}",z); //later use z outside of match statement
}

No. All variables must have a single, fixed type. However, you could define an enum with one branch holding an i32 and the other holding a string.

3 Likes

You have to have a discriminant. So in Rust it would be something like :

#[derive(Debug)]
enum MyEnum {
    Int(i32),
    String(String),
}

let z = match x {
    1 => MyEnum::Int(0),
    2 => MyEnum::String("hello".to_string()),
    _ => panic!("unexpected"),
}

is this also possible using dyn trait?

If you type something as dyn Trait, you can use this thing only as much as the trait allows. If there's some limited number of necessary actions, surely, it's possible.

1 Like

For example:

use std::fmt::Debug;
fn main() {
   
    let x = 1;
    
    let z: Box<dyn Debug> = match x {
        1 => Box::new(0), //return int here
        2 => Box::new("hello"), //return string here
        3 => Box::new(String::from("world")),
        _ => unreachable!()
    };
    
    println!("{:?}",z); //later use z outside of match statement
}
3 Likes

As the compiler says, it's an error if "match arms have incompatible types".

So what you need to do is find a way to make them the same static type, while still carrying the value you want. There's two basic ways to do this:

  1. Use an enum, like how Result is used to either return a success or an error from the same function. Either — data structures in Rust // Lib.rs provides the canonical enum for this, which gives a solution like https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=65f72829cf72778bc961aee33ce6b53f

  2. Use dynamic dispatch by type-erasing, like how OOP language often do with interfaces. For example, you could use a Box<dyn TraitWithWhatYouNeed>, which gives a solution like https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=2e5c09e78fb878e82fd3890c613251de

(Also, the --explain for this error is less good than it could be, so I made a rust bug that we could do better: Can we split `E0308` into different errors for better `--explain`? · Issue #95378 · rust-lang/rust · GitHub)

As said earlier, usually you would use enums. But (extending @2e71828's example) you can also some sort-of dynamic typing in Rust:

use std::any::Any;
fn main() {
    let x = 1;
    let z: Box<dyn Any> = match x {
        1 => Box::new(0),
        2 => Box::new("hello"),
        3 => Box::new(String::from("world")),
        _ => unreachable!()
    };
    if let Some(i) = z.downcast_ref::<i32>() {
        println!("Integer {i}");
    }
    if let Some(s) = z.downcast_ref::<&str>() {
        println!("String slice {s}");
    }
    if let Some(s) = z.downcast_ref::<String>() {
        println!("Owned String {s}");
    }
}

(Playground)

Output:

Integer 0

I think this is generally pretty bad style though, and I guess there are also some pitfalls when you use downcast. So this example isn't what you usually should do in Rust.

A dispatch without heap allocation

use std::fmt::Display;

fn main() {
    let x = 1;
    let z: &dyn Display = match x {
        1 => &0,
        2 => &"hello",
        _ => unreachable!()
    };
    println!("{}", z);
}

An enum generator

use std::fmt::{self, Display};

macro_rules! display_plain {
    (enum $name:ident $(<$l:lifetime>)? {$($tag:ident($typ:ty)),*}) => {
        enum $name $(<$l>)? {$($tag($typ)),*}
        impl $(<$l>)? Display for $name $(<$l>)? {
            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
                match self {
                    $($name::$tag(x) => write!(f, "{}", x)),*
                }
            }
        }
    }
}

display_plain!{enum IntStr<'a> {Int(i32), Str(&'a str)}}
use IntStr::{Int, Str};

fn main() {
    let x = 1;
    let z = match x {
        1 => Int(0),
        2 => Str("hello"),
        _ => unreachable!()
    };
    println!("{}", z);
}

A custom Either

use std::fmt::{self, Display};

enum Either<X, Y> {Inl(X), Inr(Y)}
use Either::{Inl, Inr};

impl<X: Display, Y: Display> Display for Either<X, Y> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Inl(x) => write!(f, "{}", x),
            Inr(y) => write!(f, "{}", y)
        }
    }
}

fn main() {
    let x = 1;
    let z = match x {
        1 => Inl(0),
        2 => Inr("hello"),
        _ => unreachable!()
    };
    println!("{}", z);
}

Does this help - GitHub - taiki-e/auto_enums: A library for to allow multiple return types by automatically generated enum. ?