Input: Gauging feelings about syntax

Hi there. I am gauging feedback for what syntax I might propose as part of my implied enums RFC. Feel free to leave feedback. Inferred types `_::Enum` by JoshuaBrest · Pull Request #3444 · rust-lang/rfcs · GitHub

Enums in Swift already use . (Foo.bar), so the "Swift-like" version in Rust would be ::Bar rather than .Bar. The fact that this syntax is already taken does not mean we should start mixing up the namespacing syntax...

I know this topic has come up a lot here before. Personally I'm of the opinion that _ should work in more places regardless of whether or not another even briefer syntax exists for enums.

1 Like

I still worry that _::Foo working suggests that _::replace(&mut a, 0) should work too, or other things like _::into_raw(x).

So when I think it's intentional that those won't work with this, I like using something that doesn't give those implications, and it being shorter and less annoying to type is a nice bonus.

I actually would like that to work! Okay to be clear, I don't want that to work. I want you to have to use std::mem and explicitly say mem::replace, but _::replace should mostly compile with an error that says those things instead of

   Compiling playground v0.0.1 (/playground)
error: expected one of `.`, `;`, `?`, `else`, or an operator, found `::`
 --> src/main.rs:3:14
  |
3 |     _::replace(&mut a, 12);
  |      ^^ expected one of `.`, `;`, `?`, `else`, or an operator

error: could not compile `playground` (bin "playground") due to 1 previous error

I think this kind of functionality is mostly implemented in IDE/rustanalyzer right now, but it seems like the compiler could assume mem, warn me about the unused must-use value, and only error at the very end of compilation with a helpful message about what I need to be explicit about.

I think this is a false comparison, _::Foo doesn’t work either unless there’s context that suggests the base type in Rust’s existing inference logic. It’s like using Bar::<_>::Baz in a pattern. I agree it’s a bit verbose though.

@idanarye @toc @scottmcm @jrose I will not be responding to any comments here. I recommend continuing the discussion in the GitHub PR.

_::replace(&mut a, 0) should not work for the same reason _(&mut a, 0) cannot work.

1 Like

I would argue that is the case because while _::replace(&mut a, 0) doesn't work, our brains already know what it's supposed to mean.

To me that is a big win in the clarity column.

Do they? I didn't realize it meant specifically std::mem::replace until the following post mentioned std::mem. Any crate can have a function named "replace", and one might mean any one of those.

2 Likes

Personally, I assumed the _ would be replaced by some type which has an associated function or method called replace with a matching signature. The idea of _ representing a module name is quite surprising.

That, too, is a bad idea because multiple types may have a replace function with the same signature. This kind of works with traits - like the infamous <_>::default(). But that's because even without type inference, trait functions face the same issue. That is - Foo::default() already needs to infer that what we want to call is <Foo as Default>::default() (and not being able to infer that would be too much of a loss), so <_>::default() can (and should!) be thought of as inferring Default::default() rather than Foo::default().

I think type inference should stick to what I'm going to dub "The IRS Rule": if the compiler already knows what the type should be, and will punish us (with error messages) if we put any other type there, then why can't it just infer that type for us?

But if there is no such unique type (that's unique by principle, not by coincidence) then there should be no type inference.

I think this would be a good addition to the language.

There has been a concern raised previously that the current code assumes the variants are used along the enum name, so you have:

fetch(url, Cookies::Enabled)

and the new syntax makes it less clear:

fetch(url, _::Enabled)

whereas if Rust supported _:: from the start, people would use a different naming scheme:

fetch(url, _::WithCookies)

so for that I suggest paring the proposal with some kind of "alias" feature:

enum Cookies {
    #[alias=WithCookies]
    Enabled,
    #[alias=CookieMonsterSad]
    Disabled,
}

If you try to run Clippy on an enum that encodes its purpose inside the variants' names it'll yell at you:

But I think the #[alias] thing is redundant. If we are going to trust programmers to use the alias and write _::WithCookies instead of _::Enabled, why not just trust them to write Cookies::Enabled? (even if it does mean they have to import the enum)

Agreed. I think a better approach would be a clippy lint that warms when using the shorthand for unclear variant names.

But I don't think it's a big issue at all. First off all, is just Enabled really that bad? I don't consider it any worse than true. Also in many editors, you can see the parameter name hinted before the argument value anyways:

fetch(url, cookie_behavior: _::Enabled)
1 Like

Yes, but the entire purpose of using the Cookies enum is the notion that a boolean is not clear enough.

I would personally only use a unit enum like that if there are more than two variants or if it's commonly used alongside other binary arguments (that is there would be multiple bool arguments otherwise).

I believe the idea is the same as that of newtypes, to make sure you can't mix up the boolean for user accepted cookies with the boolean for being logged in as an admin (as an example). So there is a valid use for using strong types for this.

It also makes helps reduce confusion when it is not obvious which alternative should map to true and which should map to false. (Can't think of an example of the top of my head, but this has happened to me.)

And it is more extensible, let's say I have a bool is_diesel_engine. Then someone invents the electric car and now there are 3 options (petrol, diesel, electric). An enum is easier to extend (especially once fuel cell cars also showed up on the scene).

That would be under my "multiple binary arguments" point.

That would fall under my "more than two options" point.

Because there already exist complains about how long the compiler takes to do its job and adding all of this searching on top would only exacerbate the problem.