Language Design: Fixing Rust’s mistakes
Syntax
As Rust 2.0 is not going to happen, Rust users will never get these language design fixes:123
A note on the lower bar of a hypothetical Rust 2.0
An article touching on "Rust 2.0" and its reactionary reception made it apparent that language evolution has two boundaries, not one:
- Boundary 1 (upper bar of change): Things a hypothetical language "v2.0" is not allowed to improve for compatibility reasons.
- Boundary 2 (lower bar of change): Things that a hypothetical language "v2.0" needs to improve for such an effort to be worthwhile to contributors and users.
For Rust, we know the exact coordinates of the first boundary, but very little about the second boundary, as such "critical" engagement is poorly received by the community.
Nevertheless, only a cursory look is needed to conclude that "Rust 2.0" is very unlikely:
The lower bar is above the upper one, i. e. the required changes to make it worthwhile are larger than Rust leadership's acceptance for change.
This doesn't mean that we can't explore the second boundary, and collect these "unacceptable fixes" as a learning opportunity for future language designers!
Drop {}
struct initialization syntax
There is little reason why invoking functions, initializing structs and enums, and initializing tupled structs and enums have to follow different rules.4
Instead, do away with the distinction between tupled vs. non-tupled structs/enums and standardize on ()
for function
invocation and struct/enum initialization.
See this article for more details.
Named parameters using =
After dropping the special syntax for struct literal initialization, use =
to name parameters for any kind of invocations in Rust.
This allows restoring the intuition that =
is followed by a value and :
is followed by a type, and that every value can receive a type ascription.
See this article’s appendix for an evaluation of available design options.
Use []
instead of <>
/::<>
for generics
Turns out “trying to preserve the strangeness budget”5 can’t fix a broken design.
Drop range syntax
It takes up way too much language footprint for very little actual benefit, is a source of language expansion proposals and the actual implementation in Rust suffers from quite a few other problems.
Drop array and slice syntax
This frees up the []
bracket pair for more useful purposes, like type parameters/arguments.
Fold Index
and IndexMut
into Fn
trait family
Providing traits to let people decide how round they want their function call parentheses to be is not a useful feature.
Replace impl
with purpose-focused alternatives
There are roughly three different purposes for which impl
is used.
Disentangle these purposes, drop the impl
keyword, and make the replacements feel more cohesive with the rest of the language:
-
Replace
Replaceimpl
definitions that add methods to a struct/enum/... with methods in the struct/enum/... body.
withstruct Foo { bar: Bar } impl Foo { fn qux() -> Qux { ... } }
struct Foo(bar: Bar) { fn qux() -> Qux { ... } }
-
Replace
Replaceimpl
withtrait
in definitions that implement atrait
for a given struct/enum/... type.
withimpl SomeTrait for Type
trait SomeTrait for Type
-
Replace
Replaceimpl
in definitions that hold "extension" methods.
withimpl<T> Option<Option<T>> { ... }
trait OptionExt<T> for Option<Option<T>> { ... }
Stop using macros to emulate varargs
Macros are largely used to work around the lack of varargs in Rust.6
Instead, extend Rust’s vararg support from extern
functions to all functions.
Drop ::
The distinction between path navigation (::
) and member access (.
) is not important enough to bother users at every single occasion.
Instead, let the IDE use some syntax coloring and be done with it.
Drop as
… or at least make it make sense: it should either do type conversions or value conversions, but not both.
Not to mention that as
caused security vulnerabilities already.7
Drop if-let
You know a feature is not well-thought-out if it has spawned 4 extensions proposals already.
Instead, use the vastly superior is
design.
Drop procedure syntax
Functions that return no useful value enjoy special syntax privileges over functions that return a value.
Drop this syntax sugar and require -> ()
to be written down explicitly, like for every other type.
Drop ->
in function signatures
No reason to have two different ways to attach a type to the preceding program element. Just use :
.
Remove significance of semicola
Varying the meaning of a piece of code based on the presence of a ;
at a specific line is bad user interface design.
Remove it and implement automatic semicolon inference, such that IDEs can show them, but no user has to ever type them.8
-
Especially considering that half the people immediately define a
::new()
functions to avoid struct initialization syntax.
Having the choice between both is already a net-negative on its own. ↩ -
All language designers hate varargs, but handing out macros as a replacement is considerably worse. ↩
-
Using u16::max instead of u16::MAX potentially allows very long certificates ↩
-
If your sole wisdom on “leaving semicolons out” is JavaScript’s ASI, then you are not qualified to have an opinion on this topic, sorry. ↩