Am I missing something? Why not LanguageType::Rust.line_comments()
?
It holds some relevant information in a few cases.
enum FoosOption<T> {
Some(T),
None,
}
match ... {
// Wait, is it `::core::option::Option::Some` from prelude?
Some(_) => {},
// The pattern might also be the source of type inference?
None => {},
}
I'd really like those choices to be visible. However, I do think I see where you are coming from and would prefer a solution that is not as heavy on symbols either. I also don't think that _
is a particular good choice in that regard either. Especially within patterns its use is for an item that is ignored, not one that is inferred. Just slightly more verbose but, imho, much more readable would be to use the keyword enum
itself as infer this enum type for me.
match FoosOption::None {
enum::Some(_) => {},
_ => {},
}
No you're not missing something. I guess I thought that was unintuitive enough that it couldn't work. Glad to know that it does, though personally I would stick to either of the previous two forms over this one.
I'm in way over my head here (first post, only been using rust a few months), but when I was first getting started with the language I expected to be able [edit, learned it's possible, so] for it to be more common to:
enum X {
Y(bool),
Z
}
use X::*;
let x: X = Y(true);
which seems like it would solve the match
usability issue without any ambiguity (in that the rules for shadowing would be the same as with other use
invocations), allow for shorter local renaming, and be consistent with (all?) other occurrences of ::
in the language. What are the downsides of this approach?
You can already do that, what we are talking about here is destructuring enums without having to explicitly bring the enum variants into scope. Like this
enum X {
Y(bool),
Z
}
match x {
Y(_) => todo!(),
Z => todo!(),
}
// or
match x {
_::Y(_) => todo!(),
_::Z => todo!(),
}
I don't understand @jbr as saying you cannot do that. But I do find it compelling that they are new to the language and have expectations that align well with how the language works. That suggests to me that things work fine as they are -- and that learnability won't be improved by adding more special cases. Indeed, I largely do agree that use X::*;
is good when necessary, and agree with @petrochenkov that Foo::Variant
reads better. There's also Self::Variant
which is often usable.
Thanks, I didn't realize that worked! I apparently don't have the usability problem presented by OP
It isn't very informative when reading code. But it helps when writing code, especially for newer users, because it makes the mental model of how names come into scope simpler and more consistent. Having an explicit marker also means we can support the syntax in all contexts, like Swift does, not just in match patterns. In particular, I think it would be a big win in function arguments.
--
On the other hand, I strongly dislike the specific proposed syntax _::Foo
. _::
is annoying to write (hard to type without significant hand movement on a QWERTY keyboard), and annoying to read (too long). If you need something that heavyweight, you might as well just write out the enum name instead, which is even heavier but at least compensates by being informative. I'd prefer a lighter-weight syntax, like :Foo
.
Does anyone have a sense of how often matching against const
s is used? While we now have good lints for the problem, the confusion between bindings and consts is well-documented. (It's even in SML criticisms from 1998, showing how must Rust is actually ML in C clothing )
I'd be really tempted to require const Bar =>
or const { 2 * N } =>
or similar for non-literal constants. Or {}
, like in const generics. Or something.
In rustc
, it's used somewhat frequently to pattern match on Symbol
s, using the always qualified form sym::my_symbol
(use rg "sym::" src/lib*
). The use there makes the code more readable in my view and I don't think const Bar
would be helpful.
It's also used relatively commonly with extensible data structures, using associated consts as a way to represent known variants of non-exhaustive enums defined by external documentation (example above).
I think from teachability perspective not requiring any prefix is the simplest. You match on an enum, you use a name of the variant, and it just works. When there's nothing needed but the enum variant name, any novice user can just type the first thing they expect to work (whether it's namespaced or non-namespaced variant name), and it will work.
Wheras with _::
, you first have to know it exists. It's possible to guess it's a thing from knowing _
and ::
, but IMHO it's a clever mashup of two features, and not something immediately obvious. Especially that other languages don't have same syntax, and AFAIK only Swift has any syntax for this at all. When reading code, an unfamiliar user could still guess what it means, but it requires stopping and mentally processing that syntax.
_::
would still be useful for function arguments, so I wouldn't mind if it was in the language, but for patterns it's less ideal than just using variant names.
Probably not for Java folks. Java variants are in scope for switch
statement only. It felt weird at first but learn it we did. Now feels very convenient - less boilerplate. Brains adjust. People will learn and likely appreciate; even if variants are not in scope for anything else.
The problem is when you then try to use the name of the variant somewhere else, and it doesn’t work. At least, that’s the biggest concern I have from a teachability perspective.
I get this is a problem, but the compiler already knows how to fix this error, so it's not a dead-end for users, but a matter of following compiler's suggestion.
let foo = Bar;
help: possible candidate is found in another module, you can import it into scope
|
1 | use crate::Foo::Bar;
|
This particular error suggestion could be improved to give enum-specific fix. So assuming the compiler can help with the right syntax, the remaining difference is what the nicest syntax can look like.
Ah, thanks. Probably too much churn to consider, then.
It still requires the user's mental model to be more complex if they want to actually understand when they need the use
and when they don't, as opposed to guessing and hoping the compiler will give a good suggestion if they guess wrong.
...Perhaps not much more complex. I may be biased against the idea because of its resemblance to C++'s horrid argument-dependent lookup and other namespacing rules, even though the rule being proposed here is far, far less complex.
Still, it's important to keep in mind that even perfect diagnostics don't eliminate the cost of complexity, only reduce it. And while the diagnostic you mentioned is quite good, it's not perfect, at least not in the sense that the user can mechanically accept the suggestion whenever they see it. After all, there isn't always just one candidate, and sometimes none of the candidates are correct (because your actual mistake was something other than a missing import – e.g. the name might be intended to refer to something defined in the current module, but you forgot to define it).
I think this is the point where we need to compile a weighted list of pros and cons, and make a principled decision from there.
Otherwise this will just go back and forth for weeks until everybody forgets about this. It really doesn't seem like further debate is going to bring more information, just differences of taste.
With string literals inferring enum
, it'd work pretty well, no compatibility break.
match x {
"bar" => ...
"baz" => ...
"quux" => ...
"another-variant" => ...
}
This'd be appropriate for tagged enums. Every variant'd consist of (str, num) at compile time. (str is auto selected while defining variants.)
Using string literals for enum
s has been a convention in other languages (ActionScript 3.0 for instance), but in Rust it can just be static (no dynamic string involved).
What if this were applied to non tagged enum
s? Let's see:
match x {
"ok"(x) => ...
"err"("expecting-token") => ...
}
This could be supported, not sure.
This is way too different from existing Rust semantics.