"enum as repr" constants in match (and other patterns)

In a discord discussion today, the question of using enum variants in a match for another type came up.

Given an enum and a u8

let x: u8 = 0;

#[repr(u8)]
enum A {
    B
}

the goal is to use the enum discriminants for matching a u8 value. The simple “obvious” case doesn’t work, because of a type mismatch:

match x {
  A::B => ..,
  _ => ..,
};

it feels like you should be able to use as to get the types to line up, but that doesn’t work either (as is not part of pattern syntax):

match x {
  A::B as u8 => ..,
  _ => ..,
};

instead you need to indirectly put the as elsewhere via one of these less-than-ideal workarounds:

const B_U8 : u8 = A::B as u8;
match x {
  B_U8 => ..,
  _ => ..,
};

match x {
  _ if x == (A::B as u8) => ..,
  _ => ..,
};

Is there some other way to do this I’ve missed (not counting using macros to automate some of the repetition)

What would be necessary to / the implications of allowing match arms like A::B as u8 =>

It’s a const expression for a literal value (aside, the reference doesn’t seem to explicitly describe using enum variants as match patterns in its grammar, but I assume it’s effectively part of a Literal Pattern). I ask now because, perhaps with all the work being done to enable const functions lately, it’s a good time to consider.

For putting values in generic parameters the decision, IIRC, was to wrap them in {} to make it clear that an expression is coming (like how <> tells the expression parser that a type is there). We could copy that in match, or just invent const{} blocks

1 Like

With macros we can get the following syntax to work (it also ensures that expressions in case matchers are const):

use ::c_switch::switch;

#[derive(Debug)]
#[repr(u8)]
enum E {
    A,
    B,
    C,
}

fn main ()
{
    let n = ::std::env::
        args()
            .nth(1)
            .and_then(|argv_1| argv_1.parse().ok())
            .unwrap_or(0)
    ;
    let x = switch!{ (n) where cases are u8 :
        case E::A => E::A,
        case E::B => E::B,
        case E::C => E::C,
        case 42 => {
            println!("Found easter egg");
            return;
        },
        default => {
            eprintln!("Invalid input number");
            return;
        },
    };
    dbg!(x);
}

How do you feel about it @dcarosone?

I’m aware of several crates that use macros for similar purposes. I left them aside specifically because I wanted to focus on constants/literals in pattern syntax, match just being a prominent example. I now realise I somewhat buried that intent by the time the post was written:

(More on this below)

However, you example does demonstrate an interesting point, which is that the could be a place for some annotation or cast at the match level for all arms at once:

But really, the point remains that the match is already looking for u8, so telling it again that the arms should be taken as u8 seems superfluous, at either position.

So to focus on something a little more specific, let me highlight two particular questions relevant to current work-in-development.

at the pattern / match site

I was trying to figure out, in the totally vanilla case of matching variants of a plain enum against a value of the same type, which item of The Reference chapter on patterns describes the use of enum variants. I initially thought it might be literals and just not called out expicitly. I thought it might be a somewhat-hidden special-case of “Tuple Struct Patterns” even where there was no tuple as part of the variant. Turns out, it’s “Path Patterns” at the very end, in the same place where constants are.

So one possible route here is to augment the ‘path pattern’ syntax with some kind of cast, or a static equivalent, which I guess overlaps with current work on “type ascription”. If I’ve guessed correctly, other than the as syntax already wished-for, what might this look like?

at the enum definition

Another possible route would be to have something declared on the enum itself (if #[repr(u8)] can’t somehow already be enough) that allows enum variants to be taken as constants of the desired type. This was what the const B_U8 did, and what I understand several macro crates do. In this case, I’m guessing that the current work on const functions and expressions could lead to some more inherent way to get at a const of the suitable type, maybe something akin to a const version of into(). If I’ve guessed correctly, what might this look like?

Extending the pattern grammar with PAT as TYPE seems like a small extension that fits well with the current expression grammar. Of all the suggestions in this thread, this one seems the most natural in a “it flows out of the current language” sense. In particular, if we gain PAT : TYPE ascription in patterns as proposed in https://github.com/rust-lang/rfcs/pull/2522, then as-patterns makes even more sense. However, I would wait with this until the type ascription RFC gets unstuck.

3 Likes

@Centril thankyou!

I knew of the earlier RFC (803) though it had been a while and I didn’t recall (nor go back to check) the details, beyond it mostly being about letting expressions have the same : TYPE as let bindings already did.

I did not know at all about the new one you linked, and indeed it seems very closely related, even to making the point that the let binding site is currently a special case within a pattern (rather than, as I assumed, after it) and should be generalised.

So, whether via a path to coerce the enum variant to a u8, or with a natural later extension for as-patterns, if this all falls naturally out of current work, that’s great - and in the meantime we have several macro-based options.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.