top | item 23246908

Things I hate about Rust

163 points| woodruffw | 5 years ago |blog.yossarian.net

121 comments

order
[+] twic|5 years ago|reply
> No current way to get the user’s home directory. std::env::home_dir() is explicitly marked as deprecated, and the documentation encourages users to rely on the dirs crate (which is currently archived on GitHub).

Yeah, this is a bit of a farce. On the one hand, it seems fair to me to not have this in the standard library - it's not something that needs special cooperation with the compiler or runtime, it's not something universal (what would it do on an embedded microcontroller?), and it doesn't matter if different crates use different implementations. On the other hand, deprecating a standard library function in favour of a crate owned by a random user seems like an unforced error.

There's discussion here:

https://www.reddit.com/r/rust/comments/ga7f56/why_dirs_and_d...

Including the fact that there is an actively maintained alternative:

https://github.com/brson/home

And a clue to why those repos are archived:

> I was wondering the same thing and contacted the author. He said that some people had "lost their shit" over some of the things he mentioned about rust and that to help them calm down, they will be archived until 2022.

!

[+] jayd16|5 years ago|reply
>what would it do on an embedded microcontroller?

Throw an exception or return null?

[+] msla|5 years ago|reply
> it's not something that needs special cooperation with the compiler or runtime, it's not something universal (what would it do on an embedded microcontroller?)

It's filesystem access, like the open file and read file functionality, so it needs some runtime support, and all filesystem access would fail on embedded microcontrollers, so I'm not seeing why it shouldn't be in the standard library.

[+] DrBazza|5 years ago|reply
If you think Rust strings are bad, you're obviously never developed on Windows.

From my failing memory, MBCStr, CComBStr, BStr, CString, XLString, and then all the unicode versions. Then you're developing in C++, so you also have std::string and std::wstring, oh and u16string, and u32string. Plus char, and wchar.

[+] dijit|5 years ago|reply
For getting a users homedir I fall back to POSIX[0] (which windows follows in this instance fwiw).

There are some things that I'm bitten by as a rust newbie though; strings are definitely one of those, since it's more difficult to reason about strings as they're heap allocated, where as most "normal" types are not, thus to use Strings you have to learn the borrow checker..

Another issue I have is crate versions, and compiling and including _multiple versions_ of the same crate, because that crate is also a dependency on one of the crates I'm using.

Yet, another is crates that only support the nightly compiler, which you're not supposed to use for production, there needs to be some way of delineating the "stable" version of a crate with the "potential future" version of a crate.

Even if you stick to a version and the nightly compiler supercedes then you end up with the same error:

------8<------

    Caused by:
      process didn't exit successfully: `/Users/dijit/projects/rust/Rocket/target/debug/build/rocket_codegen-6474f79f6391da32/build-script-build` (exit code: 101)
    --- stderr
    Error: Rocket (codegen) requires a 'dev' or 'nightly' version of rustc.
    Installed version: 1.43.1 (2020-05-04)
    Minimum required:  1.33.0-nightly (2019-01-13)
------>8------

[0]: in POSIX the OS _must_ set $HOME: https://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd...

[+] wtetzner|5 years ago|reply
> since it's more difficult to reason about strings as they're heap allocated

I don't think them being heap allocated is the issue. I think the issue you're running into is that String doesn't implement the Copy trait, meaning they're not automatically copied for you. Which is what you want, because you don't want to be implicitly copying large strings without realizing it. But yes, it does mean you have to learn the borrow checker.

[+] gfxgirl|5 years ago|reply
I'm not sure about home dir but a thing I've needed is a standard place to store app stuff. On Windows it's a mess. There's AppData/Local, AppData/LocalLow, AppData/Remote and many apps store a ton of stuff in their app folder in c:\programs etc... On Mac I guess it's Library/Application Data but there's a bunch of other folders under Library. On Linux I read somewhere it's .config but there was no .config on my Linux installs and it seems strange to me that apps should be required to make this folder vs the OS and that the OS doesn't tell me where it is it's just convention. But in any case, I think I wish some standard library handled this.
[+] kris-s|5 years ago|reply
My biggest gripe with Rust is that is doesn't spark joy. I intellectually agree with a lot of its goals but every time I write some Rust I just find it dreary. Maybe it's the ugly syntax, maybe it's the "code feel" of interacting with a grumpy compiler.

That said I know a lot of folks really dig it and think it's a breath of fresh air; more power to em'.

[+] carlmr|5 years ago|reply
Coming from safety critical C/C++ I'm really happy about the grumpy compiler. It catches basically every mistake where you would normally in C++ have static analysis, ASan, MSan, UBSan. With thousands of false positives with one true positive on your static analysis tool. ASan catching your memory errors (maybe) and telling you the rough location if you exercise the right part of your code in your unit tests. UBSan doing the same for undefined behavior. Never mind these are all separate compiles that only warn you after ages of running on your Jenkins server.

The package manager is a joy to work with and supports multiple versions of tools.

The edition mechanism allows the language not to be dragged down by old cruft.

The proc macros make it possible to have insanely reusable libraries, like serde.

The normal macros are part of the AST so you don't make stupid mistakes that are hard to trace.

Rust is just so good. But I think it's only obvious if you've worked with C++. Most of this doesn't seem special coming from C#. Except the performance that is possible.

The borrow checker might cost you a few minutes, but it can save you days or even weeks of debugging, and maybe it will save lives some day when Rust makes it to safety critical applications.

One thing that did bother me for a long time was RLS auto complete, which was just really slow and useless. This has been fixed with the advent of rust analyzer.

[+] aldanor|5 years ago|reply
The opposite here. Rust is the only language for me that sparks joy (I write numerical Python/C++ at work, well familiar with half a dozen other languages).

Like, I find it extremely pleasing to write any kind of prototype in Rust and make things work. I guess once you have borrow checker embedded in your head and your newly written code compiles without errors most of the time, it becomes more pleasing, but that's not even the main point. There's something about it that's "just right" for me, not anything in particular but a multitude of various things.

[+] jen20|5 years ago|reply
The `dirs` crate has been recently forked and revived - the new home is here: https://github.com/xdg-rs/dirs (and the crate is named `dirs-next`).

Given the relatively sparse nature of the standard library, and the cost of making mistakes (std::sync::mpsc, anyone?!) I can see why this is omitted. IMO it’s a fair criticism that pulling in lots of crates is less than ideal, though.

[+] rukittenme|5 years ago|reply
> std::sync::mpsc, anyone?!

Can anyone expand on this? I'm not well versed in Rust lore and have just used that module in a recent learning project.

[+] ChrisSD|5 years ago|reply
Perhaps Rust should make clearer that `String` is a string buffer and not just a string type. Other than that I think Rust does need different string types for different situations.

As to the standard library, I've actually started to think it's too big. I know this may sound like heresy for someone coming from a language with a big standard library. However, I think it should do whatever needs compiler support and have some traits for easier interop and that's about it. EDIT: Perhaps also some functions to help handle the more fiddly UB risks.

Rust has a great, easy to use ecosystem. More needs to be done to point out the trusted and well tested crates and to point people in that direction. The standard library itself is much harder to contribute to and has stability guarantees which can potentially make mistakes costly in the long term.

[+] twic|5 years ago|reply
There's definitely a theory that the pairs of view and buffer (or borrowing and owning) types should have been more consistently named. I think steveklabnik usually manifests in these threads to express it. But it's something like:

str is to String

as &[] is to Vec

as Path is to PathBuf

... and the names should reflect that.

[+] dralley|5 years ago|reply
That "String" should have been called "StrBuf" or something similar is a relatively widespread opinion. Oh well.
[+] http-teapot|5 years ago|reply
I have the same problem with strings as well. I think I understand the difference between String and &str but I wish it’d automatically convert String into &str when a function expects it.

Example: `std::env::var(“SOME_PARAM”).unwrap_or_else(|_| “localhost”.to_string())`

It just seems odd to me, something tells me there is a better way to do this but I couldn’t find it.

[+] woodruffw|5 years ago|reply
> I have the same problem with strings as well. I think I understand the difference between String and &str but I wish it’d automatically convert String into &str when a function expects it.

Yep! My understanding is that `AsRef<str>` is the right way to do this automatic conversion but I learned that by reading library code, not from the standard documentation. It'd be awfully nice if the compiler could do those sorts of conversions automatically; injecting `AsRef` everywhere adds a lot of visual clutter.

[+] twic|5 years ago|reply
There, you're converting a &str into a String, not String into &str. That involves allocation, so i think it's quite right that it's explicit.
[+] adamnemecek|5 years ago|reply
The strings actually make sense. The difference between String and &str is same as between Vecs and slices. Don't use OsStr unless you have to.
[+] ajross|5 years ago|reply
The problem is naming though. Calling something a "slice" connotes the existence of a whole somewhere from which the slice was taken. The string types just call themselves same thing with some funny syntax and oddly inconsistent capitalization and abbreviation conventions.

This non-behavioral stuff matters. Almost literally no one who comes to rust understands the string types with any intuition initially. And strings are really important!

Much the same thing can be said about the evolution of other areas of rust syntax (macros and attributes come immediately to mind). The final result is a collection of syntactic soup with (mostly) well-defined and internally consistent behavior, but with a syntactic expression that bears the archaeological scars of its evolution.

[+] woodruffw|5 years ago|reply
Author here: I agree that they make sense!

I think the nuance is between "makes sense" and "fits into the mental model Rust otherwise encourages," i.e. thinking about `T` and `&T` as the fundamental building blocks for ownership semantics. In that context, being unable to directly instantiate a `str` (or having `&String` be a thing that you can occasionally produce when you mean `&str) is deeply confusing to newcomers.

[+] StillBored|5 years ago|reply
A few of my own:

Declaring/initializing a medium sized array of structs, is simply too verbose if you have meaningful structure names. What is a few lines in C explodes into pages in rust.

Type inference on declarations but, not function parameters, WAT!

The scoped constructor syntax is designed to miss the concept of RAII.

Array's by themselves are nearly useless, might have just made Vec<> the default array type.

Variables end up being &mut even when its not necessary because the compiler forces the attribute to be carried along in a number of cases when its not rightfully needed.

As I've complained about before, many of the 3rd party cargo libraries need additional traits before they can be mixed with threads/mutexes unless your willing to use unsafe.

Then there are all the bad style choices, starting with the javascript like nested calling (aka obj1.call().call2().call3().call4().call5();) chains spread over whole pages of code. Mismatch braces (yah I know this one is everywhere in C/javascript/java too, doesn't make it right). Trait declarations that are scattered everywhere rather than being centralized like a C++ class.

[+] estebank|5 years ago|reply
> Type inference on declarations but, not function parameters, WAT!

This is a hard restriction in place on purpose. The compiler could perform inference for the input and output types as part of the language but that means that the actual low level signature of your function could change by changing either the implementation or the callers. This is a compromise we must have to avoid surprises in real programs that have public APIs. On the flip side the compiler doesn't have this restriction for inferring the return type. If you write a function signature `fn foo() -> _` with a body that can resolve to an unambiguous type, the compiler will give you a structured suggestion for the correct type. This way you get the benefit of inference when developing and the benefit of type safety and unambiguous documentation of your code at the cost of mild inconvenience.

> Array's by themselves are nearly useless, might have just made Vec<> the default array type.

I'm not sure what the actionable recommendation is here. Is it to have made [] the syntax for Vec? If so, that would have made Vec special from the language's point of view, when it doesn't need to. Today you can reimplement all of Vec in your own Rust code, but you can't for arrays, because the compiler and language need specific memory layout information about them.

> Mismatch braces

Could you expand? I'm intrigued what you mean by this.

> Trait declarations that are scattered everywhere rather than being centralized like a C++ class.

This enables very flexible design and composition of behavior. It feels very weird when coming from an OOP background, but I've come to prefer it after a while.

[+] qppo|5 years ago|reply
I'm confused about this list of complaints

What is verbose about declaring arrays of structs?

Arrays are fundamentally different than vectors, the same is true in Rust as in C++.

Types are only inferred for variable declaration, that works for closures too. You can't have an inferred type in a struct declaration either. It doesn't make sense for a function declaration imho, and having worked in languages that do it - I hate it. Horrible practice to drop type args from functions.

I think method chaining is really helpful, particularly the builder pattern. It's also supremely useful for abstracted logic or patterns like iterator combinatory, something that you don't find as cleanly in other systems languages.

Not sure what you mean about traits being scattered? They're interfaces like a pure virtual base class - implementing them is very similar to implementing a base class in C++. Except you can use generic arguments for implementations which makes for some really expressive mechanisms for abstraction.

[+] the8472|5 years ago|reply
> No way to invoke a command through a system shell. Yes, I know that system(3) is bad.

I think a crate that offers more shell-like convenience functions/macros for scripting would be more powerful than just adding a system()-equivalent to the standard library.

[+] vmchale|5 years ago|reply
I quite like the way Rust handles strings. Better than Haskell.
[+] nilkn|5 years ago|reply
I think it's pretty bad in both languages. In Haskell it's trivial to understand the difference between a ByteString and Text. The names give it all away. You do need to know the difference between lazy and strict values, but that's universal to the language and not hard to understand. I'd say that strings in Haskell are more annoying than hard to understand. In Rust, even figuring out why some of the string types exist in the first place and what they mean is hard, and you still have the interconversion problem, which now is not only annoying but also difficult to understand.
[+] beckler|5 years ago|reply
Not being able to execute in the current shell has been a bit of a pain...

Want to write a environment variable that lasts the lifetime of the current shell session? No way to do it.

[+] eximius|5 years ago|reply
Is that a Rust problem? How would you normally do it that fails in Rust?
[+] sergiotapia|5 years ago|reply
That string situation in Rust sounds like a lot of cognitive load. I wonder why can a language like Nim (which competes with Rust) have such wonderful dev ux where you can just go `string` and Rust has this thing?
[+] wchar_t|5 years ago|reply
Each string in Rust has a specific purpose. OsString, for instance, is for storing data which might be in, say, UTF-16 instead of UTF-8.
[+] xrisk|5 years ago|reply
Just a guess but probably because Nim uses a garbage collector and probably doesn't make the same memory safety guarantees that Rust does.
[+] jcranmer|5 years ago|reply
There's two separate issues in play here:

The first is the distinction between "owned" (i.e., heap-allocated) and "borrowed" (types). Arguably, there could have been better language support for this to fix this issue.

The second is the fact that the underlying OS rules for strings and paths do not conform to any sane string requirements on most systems. If you collapse this into normal strings, then you end up with situations where you can't interact with your OS properly.

[+] dpbriggs|5 years ago|reply
Rust prefers explicit, deal-with-the-complexity types. The OP lumped ownership-related types (String/&str) and the nightmare that is filepath encodings (OsString/OsStr).

The first case is related to ownership/borrowing as these concepts are fundamental in systems languages.

The second case is a hint that filepaths are more complex than a single string type can represent.

[+] api|5 years ago|reply
Reading this confirms that I was right to pick Go for a couple things.

My very strong opinion is that the language should impose as little cognitive load as possible. Rust seems like it makes you think a lot about Rust, which means you are not thinking about the problem you are trying to solve. Brain cycles are a finite resource.

I don't want to diss Rust too much though. I see it as a potential future replacement for C/C++ for bare metal stuff where you really do want to hand craft the code for maximal performance or directly interface with hardware. That is a different niche than building higher level stuff, and I don't think anyone has managed to make a language that is good for both (yet?).