Extracting a value -- take two

Please bear with me on this. I already have a similar topic on this forum where I've received some good help, but this one is enough different that creating a new topic makes sense. Consider the following code:

pub enum MyEnum {
    Fred(String),
    Harry(u64),
    Andrea(i64),
}

fn main() {
    let item: MyEnum = MyEnum::Harry(45);
// Note that this needs to work whichever of the three variants
// is contained in `item`.

    match item {
        MyEnum::Fred(value) => {
            println!("\n The value is {}", value);
        }
        MyEnum::Harry(value) => {
            println!("\n The value is {}", value);
        }
        MyEnum::Andrea(value) => {
            println!("\n The value is {}", value);
        }
    }
}

The above snippet works just fine. The next step is to break the match statement out into a function that returns the value that is wrapped up in the enum variant. Here's my attempt:

fn main() {
    let item: MyEnum = MyEnum::Harry(45);
    let ddt: u64 = get_value(&item);
}

fn get_value <T> (item: &MyEnum) -> T {
    match item {
        MyEnum::Fred(value) => {
            println!("\n The value is {}", value);
            value
        }
        MyEnum::Harry(value) => {
            println!("\n The value is {}", value);
            value
        }
        MyEnum::Andrea(value) => {
            println!("\n The value is {}", value);
            value
        }
    }
}

This throws the following error:

13 | fn get_value <T> (item: &MyEnum) -> T {
   |               -                     - expected `T` because of return type
   |               |
   |               expected this type parameter
...
17 |             value
   |             ^^^^^ expected type parameter `T`, found `&String`
   |
   = note: expected type parameter `T`
                   found reference `&String`

I've tried to get it annotated correctly, but am not getting anywhere. How do I get this working.

Thanks!

What really helped me with these kind of errors is that you need to remember that the caller (your main function) defines what T should be, not your callee (get_value). Given get_values signature, it is perfectly legal for main to call get_value::<Foo>(&item). But your function will never return a Foo, will it? It tries to return either a String, u64 or i64, but never a Foo, violating the "the caller defines the type of a generic parameter, not the callee rule." Which is why you can't use generic parameters in this case.

7 Likes

Think of it like this: What should this code do?

get_value::<std::io::ErrorKind>(&MyEnum::Harry(1u64))

There is no reasonable answer, so Rust rightfully rejects it.

You also wouldn't be able to do anything at all with it, since you don't know what type it actually is.

1 Like

Depending on your needs, you can return a trait object that has the necessary methods for whatever you'll need to do afterwards, for example like this:


fn get_value(item: &MyEnum) -> &dyn Display {
    match item {
        MyEnum::Fred(value) => value,
        MyEnum::Harry(value) => value,
        MyEnum::Andrea(value) => value,
    }
}
4 Likes

Another common approach, as seen with many serialization formats, is providing different methods for the different types that each variant may return.

impl MyEnum {
    pub fn get_fred(&self) -> Option<&str> {
        match self {
            Self::Fred(value) => Some(value),
            _ => None,
        }
    }

    pub fn get_harry(&self) -> Option<u64> {
        match self {
            Self::Harry(value) => Some(*value),
            _ => None,
        }
    }

    pub fn get_andrea(&self) -> Option<i64> {
        match self {
            Self::Andrea(value) => Some(*value),
            _ => None,
        }
    }
}

Some of the boilerplate can be reduced with a macro. This example is analogous to the as_* methods on serde_json::Value and json::JsonValue, for instance. The main point being that callers need to know what they expect to find (echoing @jofas comment).

2 Likes

If you have a variable you know is a Harry, then you can get its value with a let pattern and unreachable!:

let item = MyEnum::Harry(45);
let MyEnum::Harry(ddt) = item else { unreachable!() };
println!("{ddt}");

You didn't say what you expected or were trying to do, and that's why you have so many different answers.

Note that this is stated differently from above. (And hence the reason for returning Option.) It isn't that you know it's a Harry but that you know the type of ddt is u64.