Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow use inside match. #2830

Open
alercah opened this issue Dec 3, 2019 · 14 comments
Open

Allow use inside match. #2830

alercah opened this issue Dec 3, 2019 · 14 comments

Comments

@alercah
Copy link
Contributor

alercah commented Dec 3, 2019

Very simply, allow use to take the place of a match arm, for a tightly-scoped import. Intended primarily for work with enums, consider the following:

enum Direction {
    North,
    South,
    East, 
    West,
}

impl Direction {
    fn abbreviation(&self) -> char {
        ...
    }
}

What goes in the ...? Without use, we have to do this:

match self {
    Direction::North: "N",
    Direction::South: "S",
    Direction::East: "E",
    Direction::West: "W",
}

This is a lot of repetition and awful for long enums. We can use Direction::*;, but where do we put it? We can just put it outside:

use Direction::*;
match self {
    North => "N",
    South => "S",
    East => "E",
    West => "W",
}

But unfortunately this pollutes the rest of the function body. In this toy example, it's not that big of a deal, as we're immediately returning a value anyway. In a longer function, this could lead to less clarity or even a collision between enums with identically-named members. I've seen a scope recommended:

{
    use Direction::*;
    match self {
        ... // as above
    }
}

But this is kind of ugly and is even moreso if you are trying to use match as an expression in a bigger statement, where you might have to write:

let abbrev = {
    use Direction::*;
    match {
        ... // as above
    }
}

Instead, I propose the following simple addition to the syntax:

match self {
    use Direction::*,
    North => "N",
    ... // continued as above
}

Unless I'm quite mistaken, this is syntactically unambiguous, and while it may seem weird to use a comma rather than a semicolon after a use, there's no fundamental reason we can't put a use here, since they don't have any ordering behaviour anyway. It could desugar to the previous examples with a surrounding block, but the result would be IMO nicely ergonomic. It would also give the option of putting nested enums' use declarations directly before they're actually used in a big switch, where collisions aren't a risk.

@OvermindDL1
Copy link

OvermindDL1 commented Dec 3, 2019

If this were to be done I'd prefer to just see it on the first one instead, it obviously knows the scope for the rest by then:

match self {
    Direction::North => "N",
    South => "S",
    East => "E",
    West => "W",
}

No use or so needed as the scope is already found by this point.

@jhpratt
Copy link
Member

jhpratt commented Dec 4, 2019

@OvermindDL1 That could be confusing, as arms with just (not-in-scope) identifiers are currently a catch-all. I wouldn't be surprised if that broke code, either.

@hadronized
Copy link
Contributor

@OvermindDL1 that seems hard to do, imagine:

match (a, b) {
  (Direction::North, Something::Else) => …,
}

It would mean to apply that rule recursively to tuples / structs.

@burdges
Copy link

burdges commented Dec 5, 2019

There is visual inconsistency in @OvermindDL1 suggestion because Rust favors explicit placeholders like _::South everywhere else similar, i.e.

match self {
    (Direction::North, _) => "N",
    (_::South, Something::Else) => "S",
    _ => "Lost",
}

I think either the use or _:: sugar looks intuitive enough, so not too much strangeness cost either way, but..

I also think enum variants shadow one another rarely enough that one extra braces layer works okay:

{ use Direction::*;  match self {
    North => "N",
    ...
} }

As so often, the underlying driver for syntax changes appears to be rustfmt's bad formatting. Just write so that expressions and items are grouped by purpose, and don't use rustfmt, problem solved. That's said I'd use either of these forms if one ever stabilized.

@mehcode
Copy link

mehcode commented Dec 9, 2019

I really like the _::Variant proposal. It reminds me of a similar feature in Swift.

https://docs.swift.org/swift-book/LanguageGuide/Enumerations.html

enum CompassPoint {
    case north
    case south
    case east
    case west
}

var direction = CompassPoint.west
direction = .east // Type is known here so we can omit

In Rust this would look like

enum CompassPoint {
    North,
    South,
    East,
    West
}

let mut direction = CompassPoint::West;
direction = _::East;

Another example with match

match point {
  _::East => { ... }
  _::West => { ... }
  _ => { ... }
}

@kennytm
Copy link
Member

kennytm commented Feb 5, 2020

@Tyg13 how does it work if the match doesn't return a CompassPoint

@Tyg13
Copy link

Tyg13 commented Feb 5, 2020

@kennytm Apologies, this is what I get for posting so late at night. Indeed, it's the type of the value being matched upon that is relevant here, not the value returned from the match.

Still, the point stands -- what value does the placeholder bring?

@kennytm
Copy link
Member

kennytm commented Feb 5, 2020

You need to some indicator to distinguish between enum variants and other items.

#[derive(PartialEq, Eq)]
enum CompassPoint { N, E, S, W }

const E: CompassPoint = CompassPoint::S; // :trollface: 
use CompassPoint::N as W;

match point: CompassPoint {
    E => { /* where am i? */ }
    W => { /* where am i? */ }
    _ => {}
}

@Tyg13
Copy link

Tyg13 commented Feb 5, 2020

@kennytm, I was not aware that was possible, but it certainly makes a good case for _::. Still seems ugly in terms of syntax, though.

@shepmaster
Copy link
Member

Still seems ugly in terms of syntax,

The _ is used in a lot of other places where we want "explicit inference", like in collect:

let foo: Vec<_> = iterator.collect();

@betseg
Copy link

betseg commented Jun 25, 2020

If that's accepted would this work?

fn function(cp: CompassPoint) {}

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

@alercah
Copy link
Contributor Author

alercah commented Jun 25, 2020

I don't see a reason why it wouldn't. It really should have its own thread, though.

@nanoqsh
Copy link

nanoqsh commented Aug 23, 2021

There is a new _::Variant proposal? If not, is there an intention to create?

This is a handy feature and it fits very well in the Rust's type inference. Indeed, consistently specifying enum type in match (> 90% cases) doesn't make a code clearer, rather verbose. Also in many cases this could reduce a function calling code.

@Fishrock123
Copy link
Contributor

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests