Looking for tightest way to get stuff inside an enum

In the sample code here, all I wish to do is gain access to data inside an enum and panic ( or assert fail ) when the enum is not an expected variant.

My question is, can this be done with less code? I know a match could be used, but that's no less code than an if statement.

It seems like I'm having to write a lot of code to do something pretty simple. For instance I'm not happy with having to declare the "stuff" variable and then use a temporary "l_stuff" in order to initialize it. I'm just hoping there's an tighter way that this should be done. If not c'est la vie, I still love rust!

Thanks for any advice you may have to offer!

#[allow(dead_code)]
enum Foo {
    Bar(u16),
    Bink(u16),
}
use Foo::*;

fn main() {
    let thing = Bar(1010);
    
    let stuff: u16;
    if let Bar(l_stuff) = thing {
        println!("got my stuff!");
        stuff = l_stuff;
    }
    else {
        panic!("expected a bar");
    }
    
    println!("stuff={}", stuff);
}

I should add, I want to avoid the pattern below, because I do not want the panic to happen at the bottom of my code block and have my "harvested stuff variable" scoped only to the if let block. Imagine having to do 10 of these. I don't want to have 10 nested levels before I can gather up all my stuff.

#[allow(dead_code)]
enum Foo {
    Bar(u16),
    Bink(u16),
}
use Foo::*;

fn main() {
    let thing = Bar(1010);
    
    if let Bar(stuff) = thing {
        println!("got my stuff!");
        println!("stuff={}", stuff);
    }
    else {
        panic!("expected a bar");
    }
}

The Option and Result enums defines custom methods for stuff like this. These are called unwrap or unwrap_err. You could do the same.

Thanks for the advice. You made light bulb in my head go on!

I know about those methods of course, but is there an easy way to have my Foo enum inherit unwrap? I had not thought of this, but that's exactly what I'm hoping for here. It would seem a shame to have to write that code for each enum I would want to add that to.

Are you willing to define the 'enum' as

pub struct Foo(Result<GoodOpts, BadOpts>) ?

Wait, I spoke too fast.

I only want to unwrap the Bar variant here. I want to panic for all others. So it's not quite like unwrap.

Interesting thought, but it sounds a tad ugly. I mean, what I want to do is so simple here. Keeping it to simple enums would seem far preferable.

Going with Alice's advice, it would be nice if I could say:

let stuff = thing.unwrap(Bar(_));

I suppose I could implement a method on Foo that does this. I'm not sure exactly how though. I'll need to ponder a bit.

Better yet, something like:

#[derive(Unwrapper)]
enum Foo {
    Bar(u16),
    Bink(u16),
}

Would be really cool, but I have no idea how to implement a custom #[derive] attribute (like thingy). Is that possible? How much work is it?

Hi Alice,

Is this something like what you were thinking? (I'm not sure what the difference between "custom method" and method is)

But if so, I think I can go with this as the best approach for me. Unless there's something cleaner! Each enum variant would need to have it's own method, but I guess that's pretty clean.

enum Foo {
    Bar(u16),
    Bink(u16),
}
impl Foo {
    fn unwrap_bar(self) -> u16 {
        match self {
            Bar(val) => val,
            _ => panic!("called `Foo::unwrap_bar()` on a  not `Bar` value"),
        }
    }
}
use Foo::*;

fn main() {
    let thing = Bar(1010);
    let stuff = thing.unwrap_bar();
    println!("got my stuff! stuff = {}", stuff);
    
    let other_thing = Bink(2020);
    let other_stuff = other_thing.unwrap_bar();
    println!("got my other_stuff! other_stuff = {}", other_stuff);
}

That said, it would be great if I could learn how to implement my own #[derive(Unwrapper)] code attributes that could automatically do this for each variant so something like these would work...

let bar_stuff = thing.unwrap(Bar(_));
let bink_stuff = thing.unwrap(Bink(_));
// or even maybe
let complex_stuff = thing.unwrap(Complex(_,_);

( This sounds like I'm getting into macro territory. )

Yes that's what I meant. And I didn't mean something special by "custom".

This pattern in general is fairly easy to avoid in a completely different, and much more concise, way:

    let thing = Bar(1010);
    let stuff = match thing {
        Foo::Bar(stuff) => stuff, 
        _ => panic!("oops")
    }
2 Likes

Okay great! Thanks.... btw/ I think I added a bit more to my last comment in case you were not notified!

Thanks! I see.

I should have thought of that! I could have even used the if let in that way too, couldn't I have, i.e.

    let stuff =
        if let Bar(stuff) = thing {stuff} else { panic!("oops") };
1 Like

Yup, which is even cleaner and removes any need to implement your own custom unwrap-s. If you're running this in a function or in a loop, you can also use the same pattern to return / break early.

Awesome! I really appreciate yours and Alices's helping me think my through on this one. Made my Friday much nicer. It's funny how I can spend hours on different parts of my code, and then simple things like this require a little bit of a remake on how I think about how to write the best code.

Rust continues to be great in countless ways!

1 Like

It's pretty common to define functions for the variants like this:

impl Foo {
    fn bar(self) -> Option<u16> {
        if let Foo::Bar(x) = self { Some(x) } else { None }
    }
    fn bink(self) -> Option<u16> {
        if let Foo::Bink(x) = self { Some(x) } else { None }
    }
}

That way you can later do thing.bar().unwrap() to get the value out.

(There's probably a proc macro crate to auto-generate these, but they're trivial enough to do by hand that I haven't bothered.)

3 Likes

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.