`_` for enum

I have some code like

fn used_columns(&self) -> ColumnSet {
        match self {
            Scalar::BoundColumnRef(scalar) => scalar.used_columns(),
            Scalar::ConstantExpr(scalar) => scalar.used_columns(),
            Scalar::AndExpr(scalar) => scalar.used_columns(),
            Scalar::OrExpr(scalar) => scalar.used_columns(),
            Scalar::ComparisonExpr(scalar) => scalar.used_columns(),
            Scalar::AggregateFunction(scalar) => scalar.used_columns(),
            Scalar::FunctionCall(scalar) => scalar.used_columns(),
            Scalar::CastExpr(scalar) => scalar.used_columns(),
            Scalar::SubqueryExpr(scalar) => scalar.used_columns(),
        }
    }

Is it possible to provide a sugar like Enum::_(x) => x.func()?

1 Like

Can ambassador do what you want?

1 Like

Have you tried the enum_dispatch crate?

One option is to write something like

enum X {
    A(i32),
    B(i32),
    C(i32),
}

fn f(x: X) -> i32 {
    match x {
        X::A(y) | X::B(y) | X::C(y) => y,
    }
}

(playground)

Then your proposal would be to have X::_(y) to expand to X::A(y) | X::B(y) | X::C(y). Which is probably not great because, what if I add another enum variant that doesn't have the same parameters? Like

enum X {
    A(i32),
    B(i32),
    C(i32),
    D(String, String),
}

Then suddenly X::_(y) can't be used anymore!

1 Like

What I'm thinking is that perhaps we should have pattern aliases, something like

pattern EveryX(y) = X::A(y) | X::B(y) | X::C(y);


fn f(x: X) -> i32 {
    match x {
        EveryX(y) => y,
    }
}

Haskell has something like this pattern synonyms · Wiki · Glasgow Haskell Compiler / GHC · GitLab

2 Likes

It's relatively common to have a method on the type returning an Option that acts as a proxy for a multi-variant pattern

enum Foo { Bar(usize), Baz(usize), Qux }

impl Foo {
  fn as_usize(&self) -> Option<&usize> {
    match self {
      Self::Bar(value) => Some(value),
      Self::Baz(value) => Some(value),
      Self::Qux => None
    }
  }
}

(or even single-variant patterns, just to transform into Option, though now that we have if let those are likely less useful, other than being able to ? them sometimes).

2 Likes

A macro can do something similar to this today, for at least this use case:

macro_rules! every_x {
    ($y: ident) => {
        X::A($y) | X::B($y) | X::C($y)
    }
}

fn f(x: X) -> i32 {
    match x {
        every_x!(y) => y,
    }
}

(playground)

Would an additional pattern aliases feature allow anything that is not possible with a macro?

6 Likes

This is a design choice, but if every enum Scalar variant has exactly the same data, that suggests to me that it should really be a struct Scalar with direct fields and a data-less enum kind: ScalarKind. Then you can also be sure that the compiler doesn't have to optimize away all the matching when you access the common data.

20 Likes

But we have to write many patterns for every enum like this. And if I only have one method to do this thing, I just need move the pattern's code into that method.

the problem of doing this with a macro is that if you refactor the type to for eg. remove a variant or change its payload, the type error will confusingly happen in the function that calls the macro, and not in the pattern definition itself.

It's like the difference between Rust generics and C++ templates.

Good point. It certainly seems like it would be easier to have high quality error messages if there were a specific syntax for pattern aliases.

IMHO this is not always a valid objection. Generics could also be replaced by macros (consider templates in C++), and there are good reasons why we don't go that way. Pattern synonyms can have their own signatures which are validated by the compiler, and when we encounter them in pattern position (e.g., in a match expression), the syntax should be exactly the same as normal patterns, so we know what to expect; but for macros, anything could happen in a macro invocation, because the syntax is not pre-determined and enforced by a compiler.

Together with something like ViewPatterns in Haskell it would become very useful e.g. for high-quality Rust bindings for existing libraries. Consider for example we want a high-level binding for LLVM with the ability to pattern match on its internal IR structures, it could be done by providing a series of accessor functions try_get_VariantX_from_EnumY: fn(&EnumY) -> Option<&VariantX> (backed by the foreign implementation), and define EnumY::VariantX as a (refutable) view pattern using that accessor function. Additionally with opaque types it would be possible to work with foreign libraries just like normal Rust libraries.