Rust has a number of expression productions that have the following pattern:
keyword <tokens> { ... }
where keyword
is not valid immediately after an expression. Examples include for
, while
, if
, match
, unsafe
, and so on. Most of these productions (all of them except match
, really) end in a block expression.
It's pretty common to nest these expressions in a way that seems very wasteful of indentation but which I can't figure out a better way of writing. The worst offender in my won code is match-in-for:
for x in xs {
match x {
// ...
}
}
unsafe
is another big offender in some places.
My question: is there anything specific, other than ossifying syntax, that stops us from changing the production for every ExpressionWithBlock
(that isn't a bare block or a match
) such that it ends in another ExpressionWithBlock
rather than a BlockExpression
? In other words, allow productions like
for x in xs match x { ... }
if cond unsafe { ... }
while let Some(blah) = foo match blah { ... }
async loop { ... }
for x in y for z in x { ... }
As far as I know, none of these productions are currently allowed, and I think that in some cases they would be helpful for decreasing unnecessary indentation. This isn't to say that all of the above are good ideas, but Rust's syntax is already free-form enough that you can form some pretty groddy but correct syntax already (see: the weird expressions test).
I think this is morally dual to how $pat => { $expr }
is sometimes written out by rustfmt when it wraps a match arm.
More extremely, we could imagine allowing any expression that starts with a specialized keyword instead of a block, allowing for silly things like while cond continue;
or potentially useful things like for x in xs yield x;
(in an imaginary world where we stabalize generators). I suspect this would significantly complicate the grammar (mostly around semicolons).
(One could also extend this to functions, fn foo(x: i32) -> i32 match x { ... }
, but I'm certain that would not play well with where-clauses.)
Of course, this is the "filll-in-the-matrix" version of indentation compression. Perhaps a better approach might be to identify common "double blocks" and introduce alternative syntax to deal with them.
For example, consider the somewhat obvious starting point of
for $refutable in $iter { ... }
// =>
for x in $iter { if let $refutable = x { ... } }
Though might be tempted to write for let $refutable in $iter { ... }
instead, and analogy with while let
would imply that you should break on the first match failure!
One might also consider allowing a production like unsafe $expr
, comparable to the suggested const $expr
form from RFC 2920, though those are somewhat less powerful in that you may write unsafe if cond { .. }
but not if cont unsafe { .. }
. Unclear if it matters though.