Idea: braced or-patterns -- Enum::{A | B | C}

We can use Enum::{A, B, C}, and we can match Enum::A | Enum:: B | Enum::C -- what if we combine those ideas and allow patterns like Enum::{A | B | C}?

Here's a real example from pr71231:

    PlaceContext::NonMutatingUse(NonMutatingUseContext::Inspect)
    | PlaceContext::MutatingUse(MutatingUseContext::Store)
    | PlaceContext::MutatingUse(MutatingUseContext::AsmOutput)
    | PlaceContext::MutatingUse(MutatingUseContext::Borrow)
    | PlaceContext::MutatingUse(MutatingUseContext::AddressOf)
    | PlaceContext::MutatingUse(MutatingUseContext::Projection)
    | PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow)
    | PlaceContext::NonMutatingUse(NonMutatingUseContext::UniqueBorrow)
    | PlaceContext::NonMutatingUse(NonMutatingUseContext::ShallowBorrow)
    | PlaceContext::NonMutatingUse(NonMutatingUseContext::AddressOf)
    | PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) => {

Using nested or_patterns, this collapsed a fair bit:

    PlaceContext::MutatingUse(
        MutatingUseContext::Store
        | MutatingUseContext::AsmOutput
        | MutatingUseContext::Borrow
        | MutatingUseContext::AddressOf
        | MutatingUseContext::Projection,
    )
    | PlaceContext::NonMutatingUse(
        NonMutatingUseContext::Inspect
        | NonMutatingUseContext::SharedBorrow
        | NonMutatingUseContext::UniqueBorrow
        | NonMutatingUseContext::ShallowBorrow
        | NonMutatingUseContext::AddressOf
        | NonMutatingUseContext::Projection,
    ) => {

But it's still repetitive with those Context enum names. What if we could write this instead:

    PlaceContext::MutatingUse(MutatingUseContext::{
        Store | AsmOutput | Borrow | AddressOf | Projection
    })
    | PlaceContext::NonMutatingUse(NonMutatingUseContext::{
        Inspect | SharedBorrow | UniqueBorrow | ShallowBorrow | AddressOf | Projection
    }) => {

Or you could brace it even more:

    PlaceContext::{
        MutatingUse(MutatingUseContext::{
            Store | AsmOutput | Borrow | AddressOf | Projection
        })
        | NonMutatingUse(NonMutatingUseContext::{
            Inspect | SharedBorrow | UniqueBorrow | ShallowBorrow | AddressOf | Projection
        })
    } => {

But I might not choose to go that far in this case due to the added rightward drift. Of course, I'm not certain that this example is formatted how rustfmt would approach it either.

You could also use braced or-patterns when matching with constants, e.g. std::i32::{MIN | MAX}, but I don't expect such usage would be as common.

There's some overlap in this idea with the thread "Bring enum variants in scope for patterns". In my case, you'd still have to repeat that prefix on separate match arms, but I think it has the advantage of being unambiguous syntax.

10 Likes

FWIW, this is identical to Alternative syntax for working with enums (which I linked to in the "Bring enum variants in scope for patterns" thread).

Well, that alternative syntax is aiming for much broader application, which is bound to be more controversial and more difficult to implement. They didn't mention patterns AFAICS, but I'll mention the possibility in that thread. Still, I think having this just on patterns would be simple and valuable, and it would be compatible if we later went with that broader idea.

Thinking a little more -- that syntax seems to want an inner scope as if you had use Enum::*, but my intention here is that all parts of the braced or-pattern are strictly sharing the prefix. So it would be an error to match a pattern Enum::{A | B | C | D} if Enum::D does not exist, versus treating this as a sort of scope where A, B, and C are pulled from Enum while D could resolve to something else.

Just for comparison:

With type-based lookup:

    | _::NonMutatingUse(_::Inspect)
    | _::MutatingUse(_::Store)
    | _::MutatingUse(_::AsmOutput)
    | _::MutatingUse(_::Borrow)
    | _::MutatingUse(_::AddressOf)
    | _::MutatingUse(_::Projection)
    | _::NonMutatingUse(_::SharedBorrow)
    | _::NonMutatingUse(_::UniqueBorrow)
    | _::NonMutatingUse(_::ShallowBorrow)
    | _::NonMutatingUse(_::AddressOf)
    | _::NonMutatingUse(_::Projection) => {

With type-based lookup and or patterns:

    | _::MutatingUse(
        | _::Store
        | _::AsmOutput
        | _::Borrow
        | _::AddressOf
        | _::Projection,
    )
    | _::NonMutatingUse(
        | _::Inspect
        | _::SharedBorrow
        | _::UniqueBorrow
        | _::ShallowBorrow
        | _::AddressOf
        | _::Projection,
    ) => {

With type-based lookup, or patterns, and braced variants:

    | _::MutatingUse(_::{Store | AsmOutput | Borrow | AddressOf | Projection})
    | _::NonMutatingUse(_::{
        Inspect | SharedBorrow | UniqueBorrow | ShallowBorrow | AddressOf | Projection
    }) => {
3 Likes

Currently accidental use of a variable instead of variant is a non-snake-case style warning and unused var warning. It would be nice if some future edition could elevate it to a proper error.

3 Likes

To be clear, I want Enum::{A | B | C | D} to be exactly the same as writing them expanded with the same prefix, Enum::A | Enum::B | Enum::C | Enum::D, so there are no accident variables at all.

That also means you could have things like Enum::{Ident | Struct { .. } | Tuple(..)}.

1 Like

While a concise way to write it is "nice to have", it is another place where there are then two ways to write the same thing, and the benefits then have to be large to warrant a new feature.

It would be consistent in a way, because use lines have already been given a similar treatment, and already there that grouping feature does not carry its weight IMO.

I think that bringing the enum variants into scope by some means, for example explicitly :wink: is a good way to improve the code.