Alternative syntax for working with enums

If one wants to work with enums, one either has to write out the enum name repeatedly or use a use statement. I think it would be nice to have an alternative syntax.

Let's say that I have an enum

enum Value {
   a = 1,
   b = 2
}

Now if Value implements e.g. std::ops::Add, one has to write Value::a + Value::b or use Value::*; a + b.

I'd like to propose an alternative syntax, Value::{a + b}. It's much more understandable than using use and also short.

This syntax would really shine when working with bitflags. E.g. instead of

wgpu::BufferUsage::MAP_READ | wgpu::BufferUsage::COPY_DST | wgpu::BufferUsage::COPY_SRC

one could do

wgpu::BufferUsage::{MAP_READ | COPY_DST | COPY_SRC}
1 Like

This seems to me like an unusual special case, and I'm not sure if it's worth the language syntax overhead compared to, for instance, a macro: foo!(wgpu::BufferUsage, MAP_READ | COPY_DST | COPY_SRC). Or compared to { use wgpu::BufferUsage::*; MAP_READ | COPY_DST | COPY_SRC }.

I certainly see the use case, and I'm not suggesting it couldn't be made cleaner. But I'm not sure that it's something we should add special syntax to the language for.

17 Likes

It's not all that special. Yes, macro might work, however I still think that this is superior

1 Like

Reminds me of the sum enum proposal

1 Like

In practice, I think what would actually be much nicer would be "shortcut" sugar similar to how Swift does it.

enum Foo {
  case bar, baz
}

function takesFoo(_ foo: Foo) { /* ... */ }

// Both of these are allowed
takesFoo(Foo.bar);
takesFoo(.bar);

This works in general in Swift anywhere that the appropriate type can be inferred by the compiler, and it makes APIs in Swift pleasantly briefer without loss of explicitness or clarity in my experience.

(I say this as someone who in general finds Swift to be substantially more syntax-sugary than I prefer; this is one of the only bits of Swift sugar I really miss when working with Rust.)

Edit to add: I don't mean that the way it would look if Rust adopted a similar idea would be the same as in Swift, just that a general feature that supported that kind of elision would eliminate this particular pain point and could work nicely in the language in general. We'd probably have :: or something instead.

2 Likes

I think I used to like the swift way too but since we can now use Self in enums, I might actually prefer the Rust way.

I think this proposal somewhat mirrors the way hierarchical 'use' works.

I believe the Rust way to spell this would be <_>::Bar. Unfortunately, I don't believe this is inferred quite as aggressively as in Swift, so this example does not currently work. I also think it's unreasonably sigiltastic...

I think the right answer is roughly what Josh already said.

1 Like

Self::Stuff is great, but notably only works when you're in a method for a given type; the Swift design works in all contexts, which is a nice win.

(This tempts me to write up a pre-RFC proposing that; anyone know of other pre-proposals or actual proposals in that space?)

1 Like

I think that people have tried to pitch this before but were rejected.

Ok another example. I'm writing code

range: std::f64::MIN..std::f64::MAX,

I wish that instead I could do

range: std::f64::{MIN..MAX},

This syntax makes so much sense.

2 Likes

To be fair on that example, those should be associated consts on the f64 type so it's just f64::MIN..f64::MAX which isn't as bad.

3 Likes

I've seen proposals for both enum::Stuff and _::Stuff before, though I don't remember where the threads are. I don't think they got far enough to get written up in RFC format.

1 Like

I'll see if I can see how good GitHub's and Discourse's search implementations are. :+1:t3:

Edit: Discourse's is pretty good, it turns out! Found it on the first try.

I would still love to have inference that allows _::Stuff.

5 Likes

Same. In skimming through the other discussions, it seems like an improved proposal could still work. And I'd be very curious to see what the constraints would be on our ability to make a general form of it, usable with methods as well.

I don't see the problem with the currently-available use MyEnum::*;-based solution? When it is put in the body of a method or fn that uses MyEnum variants extensively, to me it seems like the extra one liner is acceptable syntactic overhead that doesn't even pollute the namespace outside the method/fn in question.

But maybe I'm missing something?

5 Likes

I'm surprised nobody has pointed out the fact that all of the example use cases are invalid?

There is no value of Value associated with the value 3.

Those are not enums. You can't use enums to implement bitflags in rust. At least, not without losing your mind if there's more than 3 bits.

What you want to do is make working with newtypes easier. Which I'm pretty sure a lot of us also want to do. Just can't agree on how.

5 Likes

I'm surprised nobody has pointed out the fact that all of the example use cases are invalid?

This is not a good faith argument.

It's more about the spirit. I have not specified what the Add operation means, maybe it's a Z2 group. Maybe Value::{a + b} == Value::a.

1 Like

What I'm saying is, you're very specifically trying to make it easier to use binary operators on enums, and I honestly cannot even remember the last time I've used a binary operator on an enum.

8 Likes

This doesn't need to be restricted to enums. We could say that Foo::{...} creates a new scope, where all items in Foo can be used. So

Foo::{
    ...
}

is equivalent to

{
    use Foo::*;
    ...
}

where Foo can be an enum, a module, a trait, etc.

However, I don't think it justifies adding a new syntax, since it doesn't make the code significantly shorter or simpler.

5 Likes