Language Design: Fixing Rust’s mistakes

Syntax

Published on 2017-07-20. Last updated on 2025-10-14

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:

  1. Replace impl definitions that add methods to a struct/enum/... with methods in the struct/enum/... body. Replace
    struct Foo { bar: Bar }
    impl Foo { fn qux() -> Qux { ... } }
    with
    struct Foo(bar: Bar) {
      fn qux() -> Qux { ... }
    }
  2. Replace impl with trait in definitions that implement a trait for a given struct/enum/... type. Replace
    impl SomeTrait for Type
    with
    trait SomeTrait for Type
  3. Replace impl in definitions that hold "extension" methods. Replace
    impl<T> Option<Option<T>> { ... }
    with
    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

  1. “Does Rust have any design mistakes?” 

  2. label:rust-2-breakage-wishlist 

  3. Broken and un-fixable parts of Rust 

  4. 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. 

  5. The language strangeness budget 

  6. All language designers hate varargs, but handing out macros as a replacement is considerably worse. 

  7. Using u16::max instead of u16::MAX potentially allows very long certificates 

  8. 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.