I mean, it's certainly better than C++, but that's a low bar to clear.
The generics syntax (turbofish) is just horrible. The only reason it exists this way is to resemble C++ templates, otherwise generics should just be in brackets. The ::
namespacing token is pretty verbose, certainly more than the usual dot. There is no way to do implied trait bounds, or trait bound aliases, or trait bounds shared within a module. Types like Option or Result are quite common, could use a special short syntax (like Int? in Kotlin). Closures require lots of typing and syntax (again, compare with Kotlin or Scala).
There are no optional and defaulted function arguments (I don't miss writing builders).
Repeating the standard derives on types is also verbose, most types could do with a standard bunch of applicable derives, and a syntax for opt-out. Items being private by default mean that you need to write lots of pub on export-heavy APIs.
Orphan rules are just a pain.
format!()
? How about we just have format strings, like in Python? Also with proper expression interpolation, not the trimmed "only identifiers" version we have. Also, string literals could be polymorphic over the resulting string type (e.g. create String or Box<str> with the same string literal syntax). Oh, and a first-class boxing operator. No more Box::new()
.
Macros are quite middling. Better than if we didn't have them, but surely a better system could be designed, if it were a priority. Hell, syn could be part of the standard library, and also it could be prettier.
We could go even further: await and const blocks could be implicit (correct behaviour inferred from usage). Combinators on Option and Result could use a special syntax. Let's do away with braces and use indentation-based syntax, people autoformat everything anyway. Unsafe code could use quite a bit of syntax sugar (e.g. look what C does). Pin... don't even get me started. Closures could use quite a bit of sugar for simple cases (e.g. what if I could do zip(a, b).map(+)
and get a vector of sums? yes, collect() omitted on purpose).
Tons of stuff one could change if one were to design Rust for maximum legibility. Some of the stuff above is entirely incidental and could be easily changed, like syntax borrowed from C++. Some is contentious for no good reason, like optional arguments. Some is critical for ecosystem stability, like orphan rules, but could be easily dropped in a language with different priorities. Some is designed for convenience of building large-scale long-living systems (like default privacy), and could be changed in a language targeting the market of novices and small-scale products. Some help with correctness and auditability (like lack of implicit conversions), but also incur a greater cognitive burden. Some limitations exist for purely technical reasons (e.g. the backwards compatibility coupled with the fragility of type inference kill a lot of possible improvements).
Different people will disagree on what exactly makes Rust great and could make different choices, even without touching the core of borrow checker, type inference, Hindley-Milner inspired type system or traits.