@matt a subset off the top of my head:
Return type inference in crate private functions: -> _
Anonymous enums: (Ty | Other)
Partial compiles (run this test even if unrelated items have compile errors)
Allow heterogeneous types in impl Trait/have a way to return different types that implement non-dyn-safe traits: impl enum Trait
Auto-clone/debug/eq impls (instantiated on first use)
Less monomorphizartion/more variables/ easier runtime introspection
Make partial borrows work
Automatic promotion of tricky &T borrows to Arc<Mutex<T>>, even in the type definition
Relaxed dyn-safety rules
Make it easier to add a lifetime to a struct without refactoring the entire codebase to add the lifetime
Incoherent impls: impl ForeignTrait for ForeignType
Most of these don't fit with Rust. But could fit in a language that is able to talk to Rust natively.
@ekuber one I often find limiting is type inference around .into()
When returning an -> impl Trait or when having to do two conversion steps it's annoying to have to specify the type.
@ekuber lifetime specifiers are confusing for someone not ingrained with how the language works.
and the memory safety of referencing the same address/object twice (r/w)
Both good features but if you want the intuitiveness of those other languages, then these features need to be both incredibly intuitive and errors explained in a way where I can fix them.
@ekuber - Type inference on functions like closures (at least the return type)
- Easy "enums" (type unions)
- Auto wrapping Try types (Option/Result) (`return 0` automatically returns `Ok(0)`)
Besides the type unions, I don't really want this to be in public code, but I do miss those things when trying to whip something up quickly.
`rust-analyzer`'s assist for wrapping and unwrapping of Try types is great tho.
@djc @ekuber i’ve seen conflicting opinions on the SIMD question. Some say that if you follow a few rules, the optimizer will generate vectorized code for you. The benefit they cite is that you don’t ever have any platform specific code. This approach seems to be used pretty effectively by many high perf projects like Apache DataFusion (Rust) and DuckDB (C++).
The downside is that you can’t really explicitly say “I know how to vectorize this.”
@djc @ekuber I’m just an interested observer in this topic, not an expert. I’m guessing you have a different opinion? Any specific experiences you could share on where the optimizer failed?
Also I’m curious if you have any thoughts on how far a high level SIMD API could go, given that it’s so architecture specific. Seems like you’d need a lot of target feature flags.
cargo add
doesn’t work with -Z script filesfn main()->Result
in order to get ?
in main (and writing the error type requires at least one import)impl
blocks need to be updated@ekuber I was experimenting recently with which language to use in coding interviews. I picked a problem involving equality constraint solving -- given a list of equalities and inequalities between variables, return true if the list is satisfiable and false if not. The algorithm involves building and merging sets of equal variables.
In Rust I wound up needing an Rc<RefCell<HashSet<i32>>> to express a mutable, shared set of variables. The ceremony of working with this added a lot of boilerplate. The Deref trait helped but not enough. In Python it would have been trivial.
Python gets brevity from shared-mutability by default, at the cost of safety. I think to really approach that same concision, Rust would need some similar (optional?) default.
I wound up going with C# for most interviews because it is kind of the middle ground between Rust and Python -- statically typed enough to be helpful, but mutable by default for convenience.
@ekuber The biggest annoyance I have with Rust (and there are not many) is the very odd way that traits make functions available on other types. As someone reading a codebase, I rely on the `use` statements to give me some clue to understanding the rest of the code, but often I find that there are functions being invoked that were never mentioned in any `use` statement.
This is an artifact of "old coder syndrome", which involves reading the code as it is written, not reading the code with the assistance of an IDE to tell you what something means.
@ekuber I use the `#!/usr/bin/env cargo -Zscript` approach to make individual files a lot. Being able to writing Rust in single script files like Python is great.
Right now, rust analyzer doesn't auto format those properly. There's also no code completion. There's a ticket open to look at the formatting. I don't know about code completion.
Getting full rust analyzer support for individually files would be awesome.
@ekuber some refactorings like the extract method one or even dependency injection are often a pain vs high level languages.
Borrow checker get on the way, need to make the method/struct generic, add manual lifetimes (and wire them everywhere)… find traits that aren’t in scope… sometimes types can’t be named so it’s impossible…
@ekuber I regularly wish for interactivity and hot reloading.
A more practical thing: I often wish for more opportunities to use _ instead of actual types. Like in arguments to a function (func(_{ x: .. })) or the patterns in a match statement (match x { _::Variant => }). The types are fully specified by their position in the code, why type them out?
@ekuber sometimes I just want to ball up bunch of data to a struct when I don’t know the shape of my program quite yet, but they may end up having references to each other, so it ends up self-referential.
Think the example program of some random crate that has everything nicely laid out in a single main function, but extract the local vars to struct and the code to various functions, usually can’t do it quite same shaped, and now I get to think architecture way too early.
@ekuber I think I expend way more energy thinking about owned vs borrowed types than I probably need to. Partly because they’re so asymmetric (String/&str, Vec<T>/&[T], PathBuf/&Path etc.) and partly because writing .clone() always feels like a mistake.
As one possible solution, I think if you had auto-clone, you could make an alternate stdlib which offered internally refcounted types similar to Swift, and then write programs that treat those as any other primitive value.
@ekuber second, Result is very annoying to deal with, you basically need to bring in thiserror and then it’s still a lot of maintenance and fiddling. Swift’s default of “throws” and forget about it is a lot fewer gymnastics.
As a possible solution, maybe the Rust stdlib could have an AnyError type so you can always -> Result<T, AnyError> and ? always works. And add some kind of syntax so adding/removing Result from the return type doesn’t require altering the syntax of every returned expression
@ekuber Disclaimer that I'm relatively new to rust. I usually use go as my "high level language", but sometime python, shell, typescript or scheme depending on the context.
In go interfaces are satisfied automatically, without a declaration, when you implement all the methods. Meaning you can print your types without #[derive(Debug)] and if you add a String() string method you can change how it prints automatically.
I also don't love some syntax choices like impl blocks.
@ekuber it's pretty hard to do/undo lifetimes vs Arc, Arc vs Rc (rhai's impl has an abstraction for this which I kinda like), add/remove generics. In general, some refactorings in Rust are tedious. Often -but not always- balanced out (eclipsed) by certainty of "refactoring is safe, the compiler will catch me".
Advanced trait bounds syntax hard to remember
the different namespaces can be confusing (is Error an enum here or a trait?)
@ekuber
> Make it easier to add a lifetime to a struct without refactoring the entire codebase to add the lifetime
GMTA
@ekuber Two aspects that immediately came to mind are difficulties matching through pointers (deref patterns?) and limited const generics support.
It’d also be nice if the arena-related debugging experience could be better because in REAL high-level languages I’d just inspect the object pointer and get what I expect :)
@ekuber my kneejerk reaction was that "Rust is a high-level language", but.... there are quite a few things I miss coming from Python.
* List, dictionary, and set comprehensions are a joy that can't be matched by Rust's combinators over iterators.
* Generators broadly. Python NAILED this. Just write a normal function and `yield`!
* Some things really are like 10x shorter to express via "duck typing" and heterogenous collections. Obviously not very rust-y.
* Heterogenous collections are sometimes useful.
* From Swift, the easy of implementing a protocol conformance when you already have the requisite members is very nice.
* From Kotlin, delegation is nice in some situations: https://kotlinlang.org/docs/delegation.html
* Maybe Rust could do pseudo duck typing with a combination of the above and an "anonymous trait" impl that is automatically satisfied... or something....
@ekuber trying to use rust like you would python is that rust doesn't have much in its standard library. Python comes with batteries included and the couple of modules you need for complex work (numpy, scipy and matplotlib) are also very complete.
Maybe rust doesn't need this in its stdlib but there should be one or two libraries you can cargo add that give you everything from an http client, CSV parser, high level math primitives (fft, firwin etc), graphing, html templating etc.
@ekuber my biggest issue is generalising code.
Recently I had a bunch of functions which operated on a struct 'Puzzle'. I wanted to generalise it to work on other types of puzzles. In python I would just give another object "like Puzzle", in C++ it would be templates.
In Rust I eventually gave up trying to build a collection of traits which expressed all the things I needed about my class and its member functions.