(This is a follow-up to Placeholder Syntax)
Hey Rustaceans,
Can we get placeholder syntax in Rust?
One of the things I really miss from Scala is the placeholder syntax, and I'd love to see at least a limited version of it in Rust.
To plagiarize this 2021 post on the topic, the syntax lets you transform code like this:
users
.iter()
.filter(|x| mypredicate(42, x))
.map(|x| x.name)
.collect();
into this:
users
.iter()
.filter(mypredicate(42, _))
.map(_.name)
.collect();
Semantically, all it does is replace the underscores (the anonymous arguments) with named closure arguments, so foo(_)
desugars to |x| foo(x)
, _.bar
desugars to |x| x.bar
, _.baz(_)
desugars to |x, y| x.baz(y)
, etc.
Besides automatically providing an elegant partial application syntax, I love this syntax because it allows me to avoid naming things that don't need to be named, which would otherwise detract from the logical flow of the code. Code should be simple, and simple code shouldn't contain irrelevant details.
When this was previously discussed in 2021 the main apparent blocker was how the closure arguments would be scoped, e.g. this post.
But, I think this is a solved problem - just use the same rule as Scala. Specifically, bind at the level of the closest outer scope. Or, to quote from the Scala semantics:
An expression
e
of syntactic categoryExpr
binds an underscore sectionu
, if the following two conditions hold: (1)e
properly containsu
, and (2) there is no other expression of syntactic categoryExpr
which is properly contained ine
and which itself properly containsu
.
E.g. foo(bar(42, _))
becomes foo(|x| bar(42, x))
.
Slight tangent
The previous thread considers the possibility that Scala's simple scoping rule wouldn't prove sufficiently flexible, and suggests elaborations.
I think the best idea was to use a special token, e.g. |..|
, as a scope marker.
I think it would work like this:
- If the scope marker is not present, default to Scala rules.
- Else, anonymous arguments are scoped to the level of their closest scope marker. E.g.
foo(|..| bar(baz(42, _)))
would becomefoo(|x| bar(baz(42, x)))
.
However, while I would like to elide argument names in arbitrarily nested scopes, it's not a hill I'm willing to die on, and I'm open to the criticism that this special syntax doesn't added enough benefit to justify its inclusion.