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

Matching on an enum shouldn't require full scoping inside the match #421

Closed
rust-highfive opened this issue Oct 27, 2014 · 18 comments
Closed
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.

Comments

@rust-highfive
Copy link

Issue by mdinger
Friday Aug 22, 2014 at 18:16 GMT

For earlier discussion, see rust-lang/rust#16681

This issue was labelled with: in the Rust repository


// main.rs
extern crate really;

fn main() {
    let my_enum = really::long::A;

    // This match works with full scoping
    match my_enum {
        really::long::A => {},
        really::long::B => {},
        really::long::C => {},
        really::long::D => {},
        really::long::E => {},
        really::long::F => {},
        really::long::G => {},
    }

    // This doesn't because the scope is missing but the scope
    // doesn't add anything because it's matching variants of an
    // already defined enum.  This should work.
    match my_enum {
        A => {},
        B => {},
        C => {},
        D => {},
        E => {},
        F => {},
        G => {},
    }
}
// lib.rs
pub mod long;
// long.rs
pub enum Enumeration {
    A,
    B,
    C,
    D,
    E,
    F,
    G,
}

I was trying to de-glob libsyntax (rust-lang/rust#11983) but this is a real nuisance there. The enums are much much bigger than this.

I hope this is clear. Let me know if it isn't.

@nrc nrc changed the title [CLOSED] Matching on an enum shouldn't require full scoping inside the match Matching on an enum shouldn't require full scoping inside the match Oct 27, 2014
@leoyvens
Copy link

leoyvens commented Jul 3, 2015

Don't know what the status is on this, but as a user I'm very much in favor 👍

@nagisa
Copy link
Member

nagisa commented Jul 3, 2015

I’m somewhat against, because for uncomfortably big matches/enum paths you can use something resembling following:

    { use really::long::*;
    match my_enum {
        A => {},
        B => {},
        C => {},
        D => {},
        E => {},
        F => {},
        G => {},
    }
    }

You don’t have to import these at the top level, function or block-level import is fine. Also library/module may export unqualified enum variants as well.

Also original motivation does not apply as much anymore either, because glob imports are stable.

@mdinger
Copy link
Contributor

mdinger commented Jul 3, 2015

I see use really_long::* for this case as a bandage to a problem that shouldn't exist. The use case for glob imports seems like it'd get much smaller if this was fixed too.

You could always write it out long form if you don't like it compact.

@dhardy
Copy link
Contributor

dhardy commented Nov 10, 2016

This would be nice, but what about derivations? E.g. this would require importing names from two enums, with more potential for name collision:

match (enum1, enum2) {
    (A, X) => {},
    (B, X) => {},
    (B, Y) => {},
    (_, _) => {},
}

@KalitaAlexey
Copy link

Why should we import instead of rewrite

match (enum1, enum2) {
    (A, X) => {},
    (B, X) => {},
    (B, Y) => {},
    (_, _) => {},
}

becomes

match (enum1, enum2) {
    (typeof(enum1)::A, typeof(enum2)::X) => {},
    (typeof(enum1)::B, typeof(enum2)::X) => {},
    (typeof(enum1)::B, typeof(enum2)::Y) => {},
    (_, _) => {},
}

I like the idea. It eases match.
What possible problems it can lead to?

@sfackler
Copy link
Member

What if X is a constant (or you want it to be a binding variable)?

@KalitaAlexey
Copy link

@sfackler,

enum E {
  A
}

fn main() {
  match E::A {
    0 => {},
    _ => {}
  }
}

returns:

error[E0308]: mismatched types
 --> <anon>:7:5
  |
7 |     0 => {},
  |     ^ expected enum `E`, found integral variable
  |
  = note: expected type `E`
  = note:    found type `{integer}`

So it isn't a problem.

@sfackler
Copy link
Member

#[derive(PartialEq, Eq)]
enum E {
    A,
}

const X: E = E::A;

fn main() {
    match E::A {
        X => {}
    }
}

@dhardy
Copy link
Contributor

dhardy commented Nov 13, 2016

Worse:

#[derive(PartialEq, Eq)]
enum E { A, B, C }
const A: E = E::B;  // counter-intuitive but not illegal

fn main() {
    match E::A {
        A => { /* which A? */ }
        _ => {}
    }
}

Backwards compatibility requires that pattern A match the constant (E::B) above, even if this is totally unintuitive, and even if it's ambiguous. Then again, I'm not sure if much real code would get broken if the compiler simply made the ambiguity an error and worked in unambiguous cases.

@KalitaAlexey
Copy link

I think that the opinions from @sfackler, @dhardy are important. I have no counter-argument.
I like how it is made in Swift.

enum E {
case A, B
}

switch E.A {
.A: /* code to handle E.A */
}

But we cannot do

match E::A {
::A => {}
}

because :: means another thing.
I'd like to have .A in Rust, but I don't quite sure that it is a right thing.

@burdges
Copy link

burdges commented Nov 13, 2016

Arguably, we should make this existing ambiguity an error, or at least a warning that said which it picked, anyways :

enum E { A, B, C }
const A: E = E::B;  // counter-intuitive but not illegal

{ use E::*;
    match my_enum {
        A => { /* which A? */ },
        _ => {}
    }
}

I think that weakens the case against presented thus far.

A priori, I kinda suspect there is more to be gained from stuff like match and method syntax (.) being generous on name availability, while being aggressive about warning of possible name collisions. I've no actual argument though.

It's a whole different matter if this interacts poorly with say return type inference for fn() -> impl Trait or something. I think not, but maybe.

Also, I could imagine folks wanting trait enums eventually like they are now going for trait fields over getters and setters. I could imagine const being used for a method alias though to, so not sure this feature adds any real complexity.

An alternative might be a weak convention that enum names be kept short in libraries perhaps.

@eddyb
Copy link
Member

eddyb commented Nov 13, 2016

As always, the backwards-compatible way to solve ambiguities is to only give meaning to code that doesn't currently compile, i.e. when something doesn't resolve within a pattern.

In fact, the any of the options expressed here that are anything like syntactical rewrites before name resoluton are quite inactionable.

Rewriting a Foo(x) pattern to <_>::Foo(x) and hoping for the best inference to do its thing might work.
But that doesn't help any single example here, because, well, they all type-check.

match x { a => {} b => {} } will always get past name-resolution and type-checking when a and b don't refer to anything in scope, because they're variable bindings.
Only after type-checking can the match be recognized as having some unreachable arms.

@mdinger
Copy link
Contributor

mdinger commented Nov 14, 2016

F# in comparison is slightly better but not much smarter. It doesn't require full scoping unless if finds an ambiguity. It would look like this in Rust:

enum Two {
    Left,
    Right
}

enum Four {
    Left,
    Right,
    Top,
    Bottom
}

fn main() {
    let four = Four::Top;

    match four {
        Four::Left => {},
        Four::Right => {},
        // ^ Sees these as name collisions when in actuality they should not be
        Top => {},
        Bottom => {},
    }
}

@eddyb
Copy link
Member

eddyb commented Nov 14, 2016

@mdinger Right, and Rust could've done this if it said that a can't be a variant name and A can't be a variable name, but it's probably too late for this now.

@mdinger
Copy link
Contributor

mdinger commented Nov 15, 2016

Oh, I didn't recognize your previous statement as ranking this as currently implausible (probably due to complete lack of understanding of the compiler internals). It's pretty unfortunate something this convenient would be that compatibility breaking.

@burdges
Copy link

burdges commented Nov 15, 2016

If typeof materializes #1738 (comment), then maybe you could do roughly this with a macro :

macro_rulez! ezmatch {
    ($e:expr { $m:tt }) => {{
        let __ezmatch__ = ;
        use <typeof __ezmatch__>::*;
        match __ezmatch__ { $m }
    }}
}

I'm dubious that'd ever work because the use probably cannot depend upon the typeof, but maybe.

Anyways, if you're worried about conflicting imports inside the match then I think you could write an unambiguous version :

{ use REALLY::LONG as M; match { 
    M::A => {..}, 
    M::B => {..}
} }

@Centril Centril added the T-lang Relevant to the language team, which will review and decide on the RFC. label Feb 23, 2018
@Boscop
Copy link

Boscop commented Apr 28, 2018

Could it be done in epochs?
So in the first epoch, this would be a warning, but in the 2nd one, it'd be an error:

#[derive(PartialEq, Eq)]
enum E { A, B, C }
const A: E = E::B;  // counter-intuitive but not illegal

fn main() {
    match E::A {
        A => { /* which A? */ }
        _ => {}
    }
}

In the 2nd epoch, this would compile:

enum Two {
    Left,
    Right
}

enum Four {
    Left,
    Right,
    Top,
    Bottom
}

fn main() {
    let four = Four::Top;

    match four { // no constants with same name, type is Four, so it knows these are from Four
        Left => {},
        Right => {},
        Top => {},
        Bottom => {},
    }
}

So the purpose of the warning in epoch 1 is to allow people to adjust their code before the change is made in epoch 2.

@Centril
Copy link
Contributor

Centril commented Oct 7, 2018

I don't think we've got anything actionable (or indeed desirable due to the ad-hoc nature of what is proposed) and I am not particularly interested in using the edition mechanism for breakage.
Using Self::Variant and use Self::*; should be ergonomic enough; furthermore, if we get uniform_paths then you don't have to write self::Enum::Variant.

Therefore, I'm closing this.

@Centril Centril closed this as completed Oct 7, 2018
wycats pushed a commit to wycats/rust-rfcs that referenced this issue Mar 5, 2019
…ontroller-props

Deprecate Application Controller Router Properties
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

No branches or pull requests