One of the tools that I miss most to make code easily understandable in Rust code is easy early returns/continues. They are not are not well supported together with pattern matching. This makes code often highly nested. Which breaks the flow of the code for me, because I cannot simply read it from top to bottom anymore.
The most common thing I do to keep code easy to understand in Python, Go and C (languages I develop professionally in) is the following pattern:
if x < 10 {
return Error("x should be more than 10")
}
// do something with x
The reasons I like this pattern are:
you do any checking at the start of the function and in all code after that you know that x is fine
it's very clear that it errors out on that check
code doesn't get deeply nested.
With a simple if this is possible in Rust, but when pattern matching is involved (which it often is) the best I could come up with is this:
let thing = if let SomeVariant(thing) = an_enum {
thing
} else {
return Error("an_enum should be of type SomeVariant")
}
// do something with thing
This is a pretty verbose syntax for this pattern. That's why I would like to propose a new syntax. Something like this:
let SomeVariant(thing) = an_enum else {
return Error("an_enum should be of type SomeVariant")
}
// do something with thing
Before opening a full fledged RFC I wanted to get a bit of feedback on this idea. So some questions:
Am I missing a better way to do this pattern (or to get its advantages)?
Do you think this would indeed be a useful addition to the language?
That's actually inferior to what's being proposed here. let-else would shorten (somewhat) a common (somewhat) idiom. That other proposal basically just duplicates the complete semantics of if let with a different syntax.
(That said, I'm not a fan of present proposal either, because I'm starting to acquire syntactic diabetes from all this sugar. Yet I'd take this let-else as a valid ergonomic improvement in contrast with that other thread, because it actually adds some very minor compression and semantics. But exactly duplicating already existing functionality has no objective benefits other than a very personal "I prefer the colon to if let".)
The question here is. Do you care what "thing" is?
If so, then the let thing =match ...makes a lot of sense. if you've gone to the trouble of putting a value in a variant, you're likely to want to use it. Especially after checking it's there.
You're probably gonna want to unwrap it later anyway,
If not, Option and Result already have the is_err, is_ok, is_some and is_none methods. which handle the case you don't care what the thing is. It's not too hard to add those to a custom enum.
Or even (if you're feeling super hacky.) proc macro up an auto is_<variant>() for deriving.
By using a special token (which could be =? or bikeshed_assignment_sigils), we are allowed to requires braces, which in turn remove the ambiguity of else.
You can elide else { () }, leading to if expressions without a trailing else. These if expressions evaluate to (), but the parser has no knowledge of types so it cannot disambiguate based on that.
I don't think it'll come up, in practice. You can't in general omit the else on an if on the right-hand side of an assignment, unless the expression has type ().
Also, I'd recommend a style lint in clippy saying that if you write let x = if ... else ... else ...; then you have to put braces around the first if ... else ....
I'm talking about the whole problem, not only when there are two else clauses. The fact that even deciding whether or not it's ambiguous was hard is a red flag to me.
I like this idiom except that the identifier thing has to be written three times. Could we introduce some sugar? I don't have good generic design in mind but for example:
let thing = match an_enum {
<= SomeVariant(@),
_ => return Err("err"),
}
By introducing <= $pat match arm syntax. "@" is a sigil for the "hole" and result value of the match expression is value of that hole if $pat is matched.
let_ensure!(SomeVariant(thing) = an_enum, "an_enum should be of type SomeVariant");
// Expands to
let thing = if let SomeVariant(thing) = an_enum {
thing
} else {
return Error("an_enum should be of type SomeVariant")
}
// do something with thing