rfcs icon indicating copy to clipboard operation
rfcs copied to clipboard

Allow inferring enum variant types with `_::Variant` syntax

Open fourbytes opened this issue 4 years ago • 25 comments

I’ve recently been bouncing between Swift and Rust and have found the shorthand .variant syntax to be very useful. Having something similar in Rust would be very nice to have. As initially suggested in #2830, the proposal is to allow explicitly inferring types for enum variants by using a _:: prefix.

Examples

enum CompassPoint {
    North,
    South,
    East,
    West
}

let mut direction = CompassPoint::West;
direction = _::East;
match point {
  _::East => { ... }
  _::West => { ... }
  _ => { ... }
}
fn function(cp: CompassPoint) {}

let direction = _::West;
function(direction);

fourbytes avatar Aug 23 '21 21:08 fourbytes

You should already be able to import enum variants directly:

use CompassPoint::West

Fishrock123 avatar Aug 30 '21 17:08 Fishrock123

You should already be able to import enum variants directly:

That's true, but importing the variants can cause other name clashes:

struct Fahrenheit(f32);
struct Celcius(f32);

enum Temperature {
    Fahrenheit(Fahrenheit),
    Celcius(Celcius),
}

impl Temperature {
    fn flip(self) -> Self {
        match self {
            Self::Fahrenheit(f) => Self::Celcius(Celcius(todo!("math"))),
            Self::Celcius(f) => Self::Fahrenheit(Fahrenheit(todo!("math"))),
        }
    }
}

If we import the variants:

impl Temperature {
    fn flip(self) -> Self {
        use Temperature::*;
    
        match self {
            Fahrenheit(f) => Celcius(Celcius(todo!("math"))),
            Celcius(f) => Fahrenheit(Fahrenheit(todo!("math"))),
        }
    }
}
error[E0308]: mismatched types
  --> src/lib.rs:14:38
   |
14 |             Fahrenheit(f) => Celcius(Celcius(todo!("math"))),
   |                                      ^^^^^^^^^^^^^^^^^^^^^^ expected struct `Celcius`, found enum `Temperature`

error[E0308]: mismatched types
  --> src/lib.rs:15:38
   |
15 |             Celcius(f) => Fahrenheit(Fahrenheit(todo!("math"))),
   |                                      ^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `Fahrenheit`, found enum `Temperature`

Something like _:: would work because it doesn't cause conflicts.

As a workaround, I sometimes rename the enum to something short:

impl Temperature {
    fn flip(self) -> Self {
        use Temperature as E;
    
        match self {
            E::Fahrenheit(f) => E::Celcius(Celcius(todo!("math"))),
            E::Celcius(f) => E::Fahrenheit(Fahrenheit(todo!("math"))),
        }
    }
}

This proposal would allow omitting the use Temperature as E; line.

shepmaster avatar Aug 30 '21 19:08 shepmaster

Ah I think I understand better now - the _ would be scoped to the type in the match. I agree this (or something like it) would be very nice!

Fishrock123 avatar Aug 30 '21 21:08 Fishrock123

If no significant troubles will be found with this syntax, I think this idea will eventually be implemented in Rust. It's sufficiently natural, it doesn't increase the amount of Rust grammar/syntax to remember, and in some situations it removes useless redundancy. A disadvantage is that in some cases it's less obvious for the person that reads the code what the enum is. So, as usual, common sense is required to the programmer to avoid puzzle-style coding.

leonardo-m avatar Sep 07 '21 11:09 leonardo-m

Just for reference I'm starting to write an RFC that would permit this in patterns (nb: not construction).

jhpratt avatar Nov 02 '21 23:11 jhpratt

@jhpratt Hi! Are you still working on that RFC? I’m willing to try to write this if nobody is doing it currently.

GoldsteinE avatar Nov 30 '21 13:11 GoldsteinE

This looks very convenient indeed!

berkus avatar Nov 30 '21 13:11 berkus

@GoldsteinE go for it!

jhpratt avatar Nov 30 '21 18:11 jhpratt

Sorry to be another person on this, but I have a lot of free time right now and also past RFC experience...

@GoldsteinE are you still working on this or can I pick it up?

Did anyone write anything down yet?

Fishrock123 avatar Jan 27 '22 19:01 Fishrock123

Hi! Sorry, I too got distracted from writing this (and also I don’t have RFC experience yet). @Fishrock123 you can take it.

GoldsteinE avatar Jan 28 '22 06:01 GoldsteinE

Pre-RFC up on the internals forum: https://internals.rust-lang.org/t/pre-rfc-inferred-enum-types/16100

Fishrock123 avatar Feb 10 '22 01:02 Fishrock123

Circa 2020 I was in favor of this, but now I think this trades too much explicitness for too little added convenience compared to the existing option of the following:

use Direction as D;
match dir {
  D::North => { ... }
  D::East => { ... }
  D::South => { ... }
  D::West => { ... }
}

8573 avatar Feb 15 '22 20:02 8573

Circa 2020 I was in favor of this, but now I think this trades too much explicitness for too little added convenience compared to the existing option of the following:

use Direction as D;
match dir {
  D::North => { ... }
  D::East => { ... }
  D::South => { ... }
  D::West => { ... }
}

sooo....

match dir in Direction {
    _::North => { .. }
    _::East => { .. }
    _::South => { .. }
    _::West => { .. }
}

berkus avatar Feb 16 '22 09:02 berkus

Circa 2020 I was in favor of this, but now I think this trades too much explicitness for too little added convenience compared to the existing option of the following:

use Direction as D;
match dir {
  D::North => { ... }
  D::East => { ... }
  D::South => { ... }
  D::West => { ... }
}

sooo....

match dir in Direction {
    _::North => { .. }
    _::East => { .. }
    _::South => { .. }
    _::West => { .. }
}

Looks like you just want a syntax sugar of the following:

{
  use Direction::*;
  match {
    North => { .. }
    East => { .. }
    South => { .. }
    West => { .. }
  }
}

... and that takes us back to https://github.com/rust-lang/rfcs/issues/2830.

rami3l avatar Feb 16 '22 13:02 rami3l

There's other places than match, though. What about if let Some(_::East) = maybe_dir {}? Generalizing to anywhere patterns are permitted seems reasonable.

jhpratt avatar Feb 16 '22 18:02 jhpratt

@rami3l yes, indeed!

berkus avatar Feb 17 '22 08:02 berkus

(if it wasn't clear: I gave up on this after the very large pushback on the internals forum, maybe someone can come at it with a better argument or angle.)

Fishrock123 avatar May 10 '22 19:05 Fishrock123

I still plan on an RFC that would permit it in pattern matching (but not construction/literals). When I brought it up on IRLO a while back there was general support behind that.

jhpratt avatar May 11 '22 00:05 jhpratt

That sounds great @jhpratt. Plus you can steal a lot of the pre-RFC text that Fishrock wrote 😁

ericsampson avatar May 23 '22 15:05 ericsampson

Just a wild thought. What would prevent us from proposing

match dir {
  North => { ... }
  East => { ... }
  South => { ... }
  West => { ... }
}

without the _ like in Swift? FWIU about the compiler, the match already knows the type of dir, so it knows the variants. I assume the most complex part is implementing the compat: if any of the variants correspond to an already imported type, the imported type can take priority.

jrandolf avatar Feb 04 '24 21:02 jrandolf

without the _

I actually also mentioned this in a recent Mastodon discussion with Yosh - https://mastodon.social/@Fishrock/111817810166819135

The rest of the thread should also be visible there. Chris was thinking that this wouldn't be possible due to existing typename collision but I think there should be some way to not make that be an issue.

Additionally, this is already a warning:

fn main() {
    let a = std::time::Duration::from_secs(1);
    
    match a {
        Duration => println!("{:?}", Duration)
    }
}

I'm fairly certain that the compiler should be able to just internally prefix unknown types in matches to enum variants if:

  • the match is over an enum
  • the name in the match arm is the name of a varient of the enum

I don't really see what could go wrong there. We already know that the match has to be variants of the enum in some form if we are matching it. There could be a some trouble if the enum type is not already known somehow (maybe by way of an intermediate .into() conversion), but I am sure we could find a way to have a good error message for that.

Am i missing something? It does not seem that complicated to just omit the enum name altogether and infer it.

Fishrock123 avatar Feb 05 '24 05:02 Fishrock123

What would prevent us from proposing ... without the _ like in Swift?

Too error-prone. If you make a typo, or remove a variant from the enum but not the match, then you'll get incorrect behaviour, because Rust would treat the corresponding branch as a catch-all pattern binding the value to a new identifier. This is already the case if try to match against constants and make a typo/ Protecting against this kind of error is one of the motivations to always write constants in ALL_CAPS, because it's easier to notice when you create a new variable instead.

A major benefit of Rust's pattern matching is that you can be generally sure that the compiler has your back, catching any discrepancy between the definition and use of enum variants. If you add, remove or change any variants, the compiler will complain, making refactorings much more reliable (unless you overuse catch-all arms). Anything which decreases that reliability is thus undesirable.

afetisov avatar Mar 20 '24 13:03 afetisov

Too error-prone. If you make a typo, or remove a variant from the enum but not the match, then you'll get incorrect behaviour, because Rust would treat the corresponding branch as a catch-all pattern binding the value to a new identifier. This is already the case if try to match against constants and make a typo/ Protecting against this kind of error is one of the motivations to always write constants in ALL_CAPS, because it's easier to notice when you create a new variable instead.

I don't think this is a very solid argument against it; as I pointed out above Rust will already emit a warning in that case, and this same problem would be present with code like:

{
    use MyEnum::*;
    match my_enum {
       Variant => {}
    }
}

Fishrock123 avatar Mar 20 '24 19:03 Fishrock123