[IDEA] Implied enum types

Please note that this is my first time using this forum.

I saw a similar issue, but it was closed due to inactivity.

  • Feature Name: Implied enums

Summary

A proposal to allow enums to be implied when inputting parameters.

Motivation

When I tried swift I noticed how simple it was to call functions without having messy enum definitions taking up space. I have used this syntax and believe it is quite convenient.

I have seen a similar issue but it has been closed.

Proposed solution

I think we could have a similar syntax that could fit with the rust language.

enum Target {
    Everyone,
    World
}

fn greet(target: Target) {
    println!("Hello {}", match target {
        Everyone => "everyone",
        World => "world"
    });
}

fn main() {
    greet(::World);
}

I think all implied enums should be like this rather than maybe an _:: because IDEs can show the implied enum:
Screenshot 2023-02-15 at 10.51.34 AM

Prior art

Swift syntax:

enum Target: Int {
    everyone = 0
    world = 1
}

// Yes, you could use "rawvalue" but this is an example
func greet(target: Target) {
    switch target {
        case .everyone:
            print("Hello Everyone!")
        case .world:
            print("Hello World!")
    }
}

greet(.world);
2 Likes

FWIW you can of course use Target::*;, which is how the standard prelude brings the variants of the Option and Result enums into scope.

4 Likes

Just ::World doesn't work well, because ::name is already allowed, with the semantics of name being the name of an external crate.

7 Likes

Reusing my posts from the last few times:

13 Likes

Sorry, you mentioned many different things within these posts. I see that you are saying this won't work because of:

  • Rust enums being different
  • Unclear code

Is this correct?

If so, one of my main ideas was to use IDE intelligence to display the actual enum. For rust enums being different, couldn't we just import them in the background?

Also I have seen the playground but this doesn't seem relevant doc.

As stated, I'm new to this forum, please help me understand .

There's also an argument that it goes against what's considered idiomatic in Rust code. You see, one of the values of Rust is to be explicit. As convenient as the feature might sometimes be, implicit enums wouldn't mesh very well with that value IMO.

Aside from that, earlier in this topic the (quite valid) point was made that you can just use use MyEnum::*;, after which you can just use the unqualified enum variant names. That is perfectly workable in practice when things would get too verbose otherwise.

2 Likes

I'd be wary of relying too much on IDE hints for readability - not everyone uses IDEs/language servers, and there's a lot of scenarios where you might need to read code without one (for example, a code review on GitHub).

8 Likes

There was also proposals to have greet(_::World); but so far they have not reach consensus. One point that was against it is that in Rust it’s idiomatic to not have the enum name repeated in the variants and eliding the enum name could to do it (like Action::ActionDown). That being said, I think that in match expression it would be useful to elide the enum name since it’s already clear about which enum we are talking about.

Yeah, thats where I got my inspiration :slight_smile:.

That is a very good point. only if github had a language server.

I will research how swift solved this.

The problem isn't just the long syntax, it's importing all these files. Also it's not neat.

Yup, I wonder what could work in place.

How are files related to this, other than that the code is contained in one?

It's not like a new sytax that requires a new feature, it literally expands from:

use caller::call;
call(/*enums::target */::hello);

To:

use caller::call;
use enums::target;
call(target::hello);

Wouldn't the it be implied by the function you're calling? Also, there are many ways to write unreadable code in rust and just like many other things, it could just be fixed by rustfmt or just a rule in rustfmt. It's opt-in. Not required O_o.


Also I think the syntax _:: is actually pretty nice and I think it would work well.

If you know the function's signature, then yes. But without an ide, and with unfamiliar code it's often not the case.

But add a new syntax just to be linted against rustfmt just feels really wrong.

That's not what I meant. What I'm saying is that this syntax could be dissalowed in some projects. Me and many other people would want to keep it on though.

What this refers to is the phenomenon of language dialects. C++ has it, but only by "virtue" of being so complex and so full of footguns that just out of pragmatism choices need to be made w.r.t. which features to use and which to avoid.

This is something I personally consider undesirable, as it just increases project complexity without a clear benefit in return.

Hmm, I don't understand. The enum is Implied from the function so there is no way to shoot yourself in the foot.

The most obvious way to shoot yourself in the foot is to have enums whose variants have the same names:

#[derive(Clone, Copy)]
enum RadioState {
    Disabled,
    BluetoothOnly,
    WifiOnly,
    Enabled,
}

#[derive(Clone, Copy)]
enum WifiConfig {
    Disabled,
    ClientOnly,
    ApOnly,
    Enabled,
}

fn configure_wireless(interface: &mut WifiInterface, rfkill: RadioState, wifi_config: WifiConfig) {
    …
}

If I call configure_wireless(&mut interface, Enabled, Disabled), meaning configure_wireless(&mut interface, WifiConfig::Enabled, RadioState::Disabled), your change means that the code compiles, even though it doesn't match what I intend. Writing out the full types correctly causes the compiler to reject the code.

And the dialect trouble C++ has is that it has enough of this sort of feature that lets you write shorter, footgunny code that different projects have non-overlapping dialects of C++; some would require me to write configure_wireless(&mut interface, Enabled, Disabled), because I must always use the short form, others would require me to write configure_wireless(&mut interface, RadioState::Enabled, WifiConfig::Disabled) because of the potential for ambiguity.

There is value in consistency, and value in keeping the language small; one of the things any proposal has to do is convince people that what it offers is more valuable than keeping the language small and consistent. There are two ways to do this:

  1. This feature is sufficiently valuable in its own right that no-one will lint against it; writing #[doc="A documentation string"] as /// A documentation string is an example here. Less obviously true, but still the case is async blocks as an easier way to implement std::future::Future than manually implementing poll correctly.
  2. The ability the feature enables is so valuable that we want it, even though people will lint against it. For example, unsafe fits this category; there are useful programs we cannot express without unsafe blocks; but people still use #[forbid(unsafe_code)] to lint against it.

In the case of your proposal, code is harder to read without an IDE, because when I see foo(::Disabled) (or foo(_::Disabled)), I have to look up the type signature of foo to work out what enum it's referring to. If I do have an IDE, then it can automatically put the enum prefix in there for me (rust-analyzer in VSCode certainly puts it in the autocomplete prompts), and could hide it from me if I don't want to see it.

Given this, how would you justify this as a language feature, rather than an IDE feature?

3 Likes