I recently went back to writing Rust code, and I still get the feeling that there are a number of “papercuts” in the language: small annoyances that nevertheless make daily programming in Rust somewhat annoying and make the language feel immature.
The ones listed there are generic enough that they should be encountered when writing any type of program, with the exception of very simple programs.
Most or all of these had issues or RFC opened, but then nothing happened, despite the fact that most of them are clearly things that should already have been implemented and several are straightforward to design and implement.
In several cases (e.g. while/for loops having values), the issue has been closed due to problems in the proposal that in fact have an easy solution.
Here’s a list of them and the improvements to avoid them:
1. Non-lexical lifetimes: https://github.com/rust-lang/rust-roadmap/issues/16
This one is essential to be able to match on something, taking references to contents, and then conditionally replacing the whole thing, without introducing an extra Option variable and conditional afterwards.
This is something that has been necessary for years, and has been blocked on MIR, but now MIR is there, so it seems like implementing this should have a very high priority.
2. General “if let”: https://github.com/rust-lang/rust/issues/929
A fundamental characteristic of C-like programming languages is that “if A {if B {…}}” can be rewritten as “if A && B {…}”.
But it is not so in Rust, due to “if let” not allowing that.
It should be fixed, by making “let A = B” where A is refutable be an expression returning bool when inside an if or while conditional (possibly anywhere not in statement position). A must not contain non-_ bindings unless at the top level or inside one or more && expression from the top level.
This way, one can write “if (let A = B) && C” and “if A && let B = C” as one would expect to be able to.
3. “else match”: https://github.com/rust-lang/rfcs/issues/1712
Just an easy improvement, avoids requiring braces, immediately obvious what it means when reading code.
4. Loop return values for while/for: https://github.com/rust-lang/rfcs/issues/961
Break with value has been added for “loop” but not for “while” and “for”, which should return Some(x) if break x is executed, and None otherwise.
The compatibility issue is easily solved by making “while” and “for” return Option<T> if and only if at least only break statement with a value is present, thus preserving the current behavior if no such break statements are present.
5. A Debug impl should be required for all types: https://github.com/rust-lang/rust/issues/28185
There is no reason for a type to not implement Debug (at worst one could just print the bytes in the memory representation or even just the struct/enum name), so all types should be required to, and it should be a trait that is always considered present on a type parameter.
For backwards compatibility, just automatically inserting #[derive(Debug)] if Debug is not otherwise implemented should be fine.
6. Warn about public types not implementing obvious traits
There are crates that expose C-like enums without an Eq, Hash, Clone, etc. implementation, and similar terrible things.
There should be a warning if any “basic” trait (including serde ones) could be derived on a type but it’s not implemented or derived, with a #[derive(!Trait)] syntax to silence the warning.
7. Make default type parameters work: https://github.com/rust-lang/rust/issues/27336
Currently if you write:
enum Enum { Abc(usize), Def(T) } print!("{}", Enum::Abc(0)).
you get an error that rustc can’t infer a type for T.
But we specified a default for T, so it should just use it…
8. impl Trait and extensions: https://github.com/rust-lang/rfcs/issues/1522
An essential feature for returning iterators that needs to be stabilized ASAP.
And also (with lower priority) extended so that you can say for instance that two functions return the same type, for instance by allowing to name the “impl Trait” type by writing "type Foo: Trait = " in the impl.
9. Non-exhaustive enums: https://github.com/rust-lang/rust/issues/32770
There needs to be a way to add additional variants on enum without breaking compatibility due to consumers being allowed to pattern match them exhaustively.
A “…” variant syntax was suggested in an issue, but it was never implemented.
Also a small improvement for structs, as it would be nicer than using a dummy private field.
10. Default struct field values: https://github.com/rust-lang/rfcs/issues/1806
There needs to be a way to add additional fields to all-pub structs without breaking compatibility.
Allowing default values on fields (with “_” meaning to just use Default::default()) would allow that
11. Generic parameters on modules and blocks
Currently if you have several items that take the same generic parameters, you have to specify them for each one, which pollutes the code with boilerplate.
For top-level items, this can be fixed by allowing modules to take generic parameters, which would be equivalent to adding them to all the items.
For non-top-level items like fns in an impl block, a simple block with generic parameters would work.
11. Auto-coerce between T and &T if T: Copy: https://github.com/rust-lang/rust-roadmap/issues/17
When using an HashMap with integer keys, you’d expect to be able to index it with an integer, but you have to take a reference every time instead; when iterating a Vec of integers, you’d expect to be use them, but instead you need to dereference them.
Coercion would avoid the annoyance and since T: Copy semantics are not really affected.
12. Associated type constructors, for<T> bounds: https://github.com/rust-lang/rfcs/issues/1598
Required for proper expressivity.
13. Merge the lazy_static, bitflags and similar crates into libstd
They are so small and fundamental that they should just be there (or in general for lazy_static, in the same crate that implements std::sync::Once).
In general, if a crate implements something that other programming language haves in the language itself, and there is no benefit in having multiple implementations, then it should probably be in libstd.
14. Merge ArrayVec, SmallVec, OrderMap into libcollections
Fundamental enough that they should be there.
Also encourages people to actually use ArrayVec/SmallVec instead of releasing slow programs because they used Vec for small things, and encourages people to use OrderMap instead of releasing non-deterministic programs because they used HashMap.
15. Compiler should recover from missing semicolons
When missing a semicolon, the compiler doesn’t have a specific error (it should say “you probably missed a semicolon”, not "expected one of those tokens’).
Also, after printing the error it should assume the semicolon and continue compiling and showing errors for the rest of the code instead of getting confused as it does now.
16. Compiler should warn about missing braces using indentation to figure out the mismatched ones
Currently the compiler doesn’t always correctly identify the missing brace.
However, almost all source code is properly indented, and the indentation can allow to reliably find the missing brace and provide an helpful suggestion to the user instead of requiring them to go on a hunt through the code.
17. Better IDE support
It seems VS code with vscode-rust with RLS is the IDE setup one is supposed to use, but it doesn’t work that well.
When typing “.”, it should always complete; when using Ctrl+click it should always go to the definition and never fail. Currently these things only work if you get lucky.