When writing #Rust code as if it were a high-level language, what is your biggest annoyance/roadblock?
Put another way, what is the biggest thing that gets in the way of Rust feeling like Python or Swift to you?
#RustLang
I have my own list, but I'm intrigued to see if there are things that aren't in my radar.
@ekuber I'd be curious to read your list, because I think I've reached the point where when I'm writing in other languages, I wish I had the guardrails of Rust, particularly around single ownership and controlled mutability.
@matt @ekuber
Same want to have more rusty elements

@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 Not being able to run tests until I've updated every bit of code during a refactor. It can be really nice in Python to get that early feedback that the refactor is doing what I want it to.
@lilyf I have some hope that the same effort to make compilers faster will provide the infrastructure to make "compile everything that this test uses and nothing else" possible

@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 For some Python cases where I'd like to switch, it is -Zscript. For general annoyances, it's const generic limitations. (I don't need turing complete, but basic arithmetic would be nice).
@chrysn a good chunk of my projects requires nightly for that reason alone (`generic_const_exprs` is there if you need it, though its syntax can be a bit "eh...")

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

@ekuber no high-level SIMD API in std. const context limitations. const generics limitations. Lack of array-based API (for example slice chunking), presumably due to const generics limitations?
slice - Rust

A dynamically-sized view into a contiguous sequence, `[T]`.

@dotstdy @ekuber oh, nice!
@djc @ekuber yeah i've had a copy pasta of the unsafe version of those functions in my own code for eons, so it's great it's made it to stable now!
@djc @dotstdy @ekuber And if anyone wants Python slices, there's: https://docs.rs/slyce/latest/slyce/
slyce - Rust

Slyce implements a python-like slicer for rust. It selects zero or more elements from an input array and returns these elements, in the order selected, as an output array.

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

@ekuber I appreciate being able to play around with a library in a REPL to see how it works for my actual use case. For example, if the library parses data from a file, what does it produce when run against samples of the corpus I'd like to use it with?
Rust crates are obviously better about describing the shape of the API than most dynamic languages but there's significantly more overhead involved in playing around with them in this way.
@tedmielczarek @ekuber For very simple things I've found the Rust Playground very useful, but it's never going to be the right tool for testing parsers and random input files.

@ekuber

  • -Z script
  • AFAIK cargo add doesn’t work with -Z script files
  • need to specify fn main()->Result in order to get ? in main (and writing the error type requires at least one import)
  • Ok(()) wrapping everywhere
  • clap’s api requires a lot more boilerplate for “trivially small” parsers than argparse so i will often just do things with std::env::args myself (this is probably telling on myself that i use bash too often lol)
  • everything is so so so verbose. BTreeMap doesn’t even support [] syntax for inserting. inherent methods can’t be renamed on import (and writing wrapper functions requires tons of type-level boilerplate). doing things the “right way” with BufRead.lines() is a lot more annoying than just using read_to_string.
  • refactoring anything type-related is tedious because the generics on all the impl blocks need to be updated
  • various rustfmt gripes that i don’t have about gofmt
@jyn @ekuber `cargo add` does work with scripts. Just like everything else, it requires a `--manifest-path` flag.
It'd be nice if we could simplify that further. At the very least, the increased use of `--manifest-path` suggests that we might want to add a short option (e.g. `-m`, or `--script`/`-s` for ease of remembering).
@epage @jyn @ekuber
Shouldn’t cargo add with a script just add the crate to the embedded cargo toml in the script file?
cargo/tests/testsuite/cargo_add/script_bare at master · rust-lang/cargo

The Rust package manager. Contribute to rust-lang/cargo development by creating an account on GitHub.

GitHub
@ekuber After using Rust for a while, dipping back into higher level languages (e.g. Haskell) made me realise how wonderful the Rust tool chain is. It's like it was designed to be usable.

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

https://github.com/rust-lang/style-team/issues/212

Styling of Rust Frontmatter · Issue #212 · rust-lang/style-team

RFC 3503 adds the concept of a frontmatter to rust for the sake of RFC 3502: cargo-script. Ideally, before stabilization, we decide on the style and get support for it.

GitHub

@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 this might be made worse by stuff that has a “big main loop”, like gui/gfx. The lifetimes now have to be ”managed” and don’t come out as naturally as it does in batch-compute cli-app.

@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 The other big thing holding me back from using rust for higher level tasks is the stdlib. In go you get a vast collection of reasonable implementations which is often what I want for higher level tasks. However, Rust's type system is so good that and many of the popular libraries are excellent so I've been using Rust more for higher level things lately anyway. Python's stdlib is obviously way too big. Maybe just a blessed list of reasonably good crates would help for Rust?
@kota @ekuber The automatic satisfaction of interfaces in go is called Structural Typing. And tbh I don’t like it. The fact that some struct is a proper implementation of a particular interface needs to be checked manually (especially if you aren’t using an IDE or LSP and is reviewing someone’s code), and you have to know about the existence of such an interface to know why a particular struct is implemented with a particular set of methods.
@kota @ekuber In Rust, instead of using `#[derive(Debug)]` to print your obj instance the default way, you could `impl Debug for YourStruct` yourself to print it in whatever way works best for you. It’s more verbose than go’s String() for sure, but verbosity is just Rust’s thing.

@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
"This library expects this return type for this function" and "Type's trait bounds were not satisfied". It's generally pretty easy for me to fix all the other issues I see but with these two it's, "welp, back to the documentation!"
@ekuber newtype deriving world be nice. And maybe deriving via. Having to import a macro crate or writing the boilerplate by hand is pretty distracting when I just want to model some types

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

@ekuber

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

Delegation | Kotlin

Kotlin Help
@ianthetechie @ekuber generators is the thing I miss the most, they remove a ton of miserable boilerplate it a lot of instances.
@ekuber The (high) level of detail needed to express a correct program. Alas, that might just be intrinsic to any language that cares about getting the details actually right.

@ekuber

  • variadic functions
  • type inference across function boundaries
  • generators (yield keyword)
  • anonymous enums
  • easier file and command APIs

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

@ekuber While LSP mostly fixes it, I do find myself doing "write code. See where rustc demands I add, or remove, some &, do so". This is particularly annoying when it's basic types like i64.