
Things I hate about Rust - woodruffw
https://blog.yossarian.net/2020/05/20/Things-I-hate-about-rust
======
twic
> 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...](https://www.reddit.com/r/rust/comments/ga7f56/why_dirs_and_directories_repositories_have_been/)

Including the fact that there is an actively maintained alternative:

[https://github.com/brson/home](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.

!

~~~
msla
> 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.

~~~
AlotOfReading
Plenty of μC's have filesystems, probably the majority nowadays. They aren't
necessarily running an operating system with a concept of users though.
Besides, in C the file commands can be used to do non-file IO, so those
functions still have a use even without a FS.

~~~
finnthehuman
>probably the majority nowadays

I'd dispute that on the grounds that a microcontroller with a filesytem is
likely doing something complicated enough that it lives on a board with at
least one other microcontroller that doesn't.

I'll even go as far to say it's more likely that the majority of
microcontrollers don't write to mutable storage other than RAM (if they have
it at all) once they leave the factory.

------
DrBazza
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_.

~~~
woodruffw
Author here: I've spent about half a decade writing Win32 applications,
including things deep in NTFS.

I agree completely that _everything_ about string typing on Windows is worse,
especially once you stray off of the happy path. But the post isn't intended
to equivocate against a ecosystem with 40 years of legacy APIs in it; it's to
nitpick a _new_ ecosystem that I otherwise love ;)

~~~
eska
I don't see an alternative to interfacing with that though. If we were to just
use UTF-8 strings for everything then there would be files we couldn't handle
in various OSs for example. So while it's a bit unwiedly to use
filepath.to_lossy_str() etc. I prefer it to a leaky abstraction that hides
necessary complexity.

~~~
jcranmer
It wouldn't be an issue if OSes forbade non-UTF-8 paths...

It's the unfortunate case that we have OS environments that are usually, but
not always, UTF-8, and there's no clear, reliable indication of whether or not
UTF-8 is to be expected.

------
dijit
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...](https://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html)

~~~
gfxgirl
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.

~~~
dijit
Yes, I understand, and I feel your plight, as it stands this is what I would
use:

$APPDATA on windows[0] or $LOCALAPPDATA

$XDG_CONFIG_HOME on linux[1], which is where you're getting the ~/.config
from.

For MacOS, it's a mess[2], you should probably use ~/Library/Application
Support/script_name/ for most things though.

[0]: [https://ss64.com/nt/syntax-variables.html](https://ss64.com/nt/syntax-
variables.html)

[1]:
[https://wiki.archlinux.org/index.php/XDG_Base_Directory](https://wiki.archlinux.org/index.php/XDG_Base_Directory)

[2]:
[https://developer.apple.com/library/archive/documentation/Fi...](https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html#//apple_ref/doc/uid/TP40010672-CH2-SW1)

~~~
jcranmer
> $XDG_CONFIG_HOME on linux[1], which is where you're getting the ~/.config
> from.

XDG_CONFIG_HOME is ~/.config if not otherwise specified.

------
kris-s
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'.

~~~
dijit
That's a shame, I find Rust (and, ruby) spark incredible amounts joy to code
in.

But I find that Go does not spark joy for me. ('.Dial'?!, first character case
denotes public/private D:).

I guess that it is very subjective.

~~~
leaveyou
'.Dial' ?? What else would you expect instead of Dial ? If you get demoralized
from an imperfect function name you are in the wrong domain of activity.

~~~
dijit
.connect() or connection.new() like everything else.

Python:
[https://wiki.python.org/moin/TcpCommunication#CA-b22bc303d6d...](https://wiki.python.org/moin/TcpCommunication#CA-b22bc303d6d0b4c7af1a18623aacd1d0900b6ff3_12)

Ruby: [https://ruby-
doc.org/stdlib-2.5.1/libdoc/socket/rdoc/TCPSock...](https://ruby-
doc.org/stdlib-2.5.1/libdoc/socket/rdoc/TCPSocket.html)

Rust: [https://doc.rust-
lang.org/std/net/struct.TcpStream.html](https://doc.rust-
lang.org/std/net/struct.TcpStream.html)

Julia: [https://docs.julialang.org/en/v1/manual/networking-and-
strea...](https://docs.julialang.org/en/v1/manual/networking-and-
streams/#A-simple-TCP-example-1)

C++:
[https://github.com/KMakowsky/Socket.cpp/blob/master/Socket.c...](https://github.com/KMakowsky/Socket.cpp/blob/master/Socket.cpp#L73)

Win32 C: [https://docs.microsoft.com/en-
us/windows/win32/api/Winsock2/...](https://docs.microsoft.com/en-
us/windows/win32/api/Winsock2/nf-winsock2-connect)

Linux C: [http://man7.org/linux/man-
pages/man2/connect.2.html](http://man7.org/linux/man-
pages/man2/connect.2.html)

Swift: [https://github.com/apple/swift-
nio/blob/master/Sources/NIOEc...](https://github.com/apple/swift-
nio/blob/master/Sources/NIOEchoClient/main.swift#L96)

Java:
[https://docs.oracle.com/javase/7/docs/api/java/net/Socket.ht...](https://docs.oracle.com/javase/7/docs/api/java/net/Socket.html#connect\(java.net.SocketAddress\))

Javascript: [https://gist.github.com/tedmiston/5935757#file-nodejs-tcp-
ex...](https://gist.github.com/tedmiston/5935757#file-nodejs-tcp-example-
js-L37)

Anyway, it's not about being demoralised, it just doesn't "spark joy". :)

~~~
dullgiulio
Go follows the Plan9 system call name instead of the UNIX one. Dial is much
more powerful than the UNIX dance of "getaddrinfo"+struct sockaddr
init+connect(2).

It might not seem much (and higher level languages usually abstract away the
craziness that UNIX sockets are in C), but that's what the OS still gives you
in 2020...

~~~
wahern
Plan 9's dial() interface is implemented in a user land library. In terms of
the interfaces provided by the kernel, connecting a socket is _anything_ but
simple in Plan 9. See
[https://9p.io/sources/plan9/sys/src/libc/9sys/dial.c](https://9p.io/sources/plan9/sys/src/libc/9sys/dial.c)

The reason no standard interface has replaced getaddrinfo + socket + connect
is probably because it's just barely simple enough for common usage in C, and
C was never the language you turned to when you wanted to write something
short and sweet--that's why Unix environments have always hosted a myriad of
other languages. If initializing a network connection were as complex as in
Plan 9, doubtless Unix _would_ have provided a more succinct libc interface
for the common case

The BSD Sockets API is also close to the simplest possible interface for
supporting all the various address and socket option combinations that are
possible. (The kernel provides mechanism, not policy.) So even if POSIX,
Linux, or whatever included a better standard interface for initializing a
connection, it would have to be in _addition_ to the BSD Sockets API (or
equivalent).

~~~
p_l
BSD Sockets are infamous for being significant issue in using _anything_ other
than IPv4 (getaddrinfo is copied over from XTI, aka Streams-related "evil"
API).

They are literally a low-level API that happened to be part of IPv4-only stack
because DoD had short deadline to get IPv4 ported to VAX and other new Unix
machines.

And OS _should_ provide a policy when it comes to networking, otherwise you
end up with never ending story of working _around_ other's software to
implement them.

------
jen20
The `dirs` crate has been recently forked and revived - the new home is here:
[https://github.com/xdg-rs/dirs](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
> 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.

~~~
jankassens
Found this post [https://stjepang.github.io/2019/03/02/new-
channels.html](https://stjepang.github.io/2019/03/02/new-channels.html)

with a link to a comment on GitHub: [https://github.com/rust-
lang/rust/pull/42397#issuecomment-31...](https://github.com/rust-
lang/rust/pull/42397#issuecomment-315867774)

~~~
jen20
Yes - sorry, this is the comment I was referring to. I'm not aware of _bugs_
in `std::sync::mpsc`, more concerns around things which could have been done
better which are now not possible to retrofit because of the guarantees of the
standard library with respect to backwards compatibility.

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

~~~
steveklabnik
Yep, you nailed it. I wouldn't change Vec though. I would make the string
types more consistent with the path types. The reason is that slices are more
general than just vectors, whereas str/String and Path/PathBuf are _virtually_
always only used together.

~~~
twic
Fair enough. Although i'd rename Vec to List, because variable-length
sequences are not vectors, and there's no need to propagate that mistake any
further!

~~~
steveklabnik
The problem is, some people think List means some form of linked list, not
what a vector is.

Names are hard.

------
thurn
Skipped over my personal biggest annoyance, the inability to abstract over
mutability (example:
[https://www.reddit.com/r/rust/comments/2a721y/](https://www.reddit.com/r/rust/comments/2a721y/))

~~~
jkarneges
Argh, I ran into this last week. I ended up just copying the code and nudging.
I wonder if a macro could be used as a workaround?

~~~
renox
Funny that's the exact same issue that C++ const cast 'solve', of course D's
inout solution is much more elegant.

------
http-teapot
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
> 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.

~~~
Ciantic
> injecting `AsRef` everywhere adds a lot of visual clutter

I agree!

`fn foo<T: AsRef<str>>(s: T)`

This is so difficult to read, one has to jump back and forth with eyes to make
any sense of these. Why couldn't there be easier syntax for "AsReffing"? E.g.

`fn foo(s: @str)`

I invented @-sign there.

~~~
steveklabnik
Syntax like @T was actually a type in ancient Rust. People really, really
hated having even more sigils in the language, so we ended up removing them.
There was also ~T.

~~~
jcranmer
IIRC, one of the sigils was for reference counting, and there was to be
another one for garbage-collected types?

(I'm recalling the lunch conversations from when I sat in the room with all
the Rust interns).

~~~
steveklabnik
@T was supposed to be a GC'd type, but ended up being a refcounted type. It
was removed before it actually turned into a GC'd type.

~T was Box<T>.

However, one issue here was that they didn't compose in the same way as types
today; ~str existed, for example, but was not the exact same as String in
terms of memory layout. Conceptually they're the same thing though.

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

~~~
adamnemecek
> 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.

That is not the case.

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

------
the8472
_> 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
I quite like the way Rust handles strings. Better than Haskell.

~~~
nilkn
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.

~~~
cryptonector
Haskell implementing strings as _lists_ of characters is a problem. Basically,
you can't use them for anything performant.

A proper string library should be a Functor, naturally, but it should also be
much more like a rope and much less like a list under the covers and in its
API.

~~~
nilkn
I agree, but that's why you use Text and ByteString. Annoying? Yes. Hard to
understand? Not at all. Rust strings to me are both annoying _and_ hard to
understand and explain.

------
nagarjun
As a Rust beginner, the point about strings really resonated with me.

~~~
dpbriggs
Over time I found that I appreciated it more and more. There's a lot of weird
and wonderful edge cases and it's a joy to see them encoded in the type
system. You don't have to resort to stackoverflow questions like this:
[https://stackoverflow.com/questions/2050973/what-encoding-
ar...](https://stackoverflow.com/questions/2050973/what-encoding-are-
filenames-in-ntfs-stored-as/2051018#2051018)

------
beckler
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
Is that a Rust problem? How would you normally do it that fails in Rust?

------
sergiotapia
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?

~~~
xrisk
Just a guess but probably because Nim uses a garbage collector and probably
doesn't make the same memory safety guarantees that Rust does.

~~~
nimmer
The garbage collector is optional and Nim is memory safe.

Just like Rust, you can unsafe structures when using libraries in C/C++.

------
api
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?).

~~~
eximius
Can't agree. Rust's goal, if you can call it that, is to make you think about
things you should be thinking about anyway.

Coming from C, you should be thinking about lifetimes and mutability and
sharing. If you dont, your program may work but be buggy. Rust forces you to
think about that.

Go does the same thing in different ways at different costs. It trades
memory/performance so that you don't have to worry about allocations and
simply doesnt use standard shared mutability patterns (not exactly, but
channels take up most of the same space).

~~~
mwindow
I don't understand why the parent post of yours is getting this much
downvotes. What's wrong with using a GC'ed language when performance
requirements are permissive?

~~~
eximius
That's not precisely their statement even if it is their intention. I don't
think anyone could argue that statement (though some may deny your axiom of
lax performance requirements).

I think people are taking issue with the claims that _Rust_ is imposing the
cognitive burden. As I said, these burdens already exist, languages just
weren't forcing people to deal with them.

