Things I hate about Rust
source link: https://blog.yossarian.net/2020/05/20/Things-I-hate-about-rust
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
May 20, 2020
Tags:programming, rant
Five years ago, Iwrote a post about the things I hated most about my then (and still) favorite scripting language: Ruby.
Today, I’m going to do the same about my current favorite compiled language: Rust.
Like the original Ruby post, these complains are personal and reflect my current best understanding of the language. Just like Ruby, they’re written from an overarching position of love for Rust.
Without further ado:
String hell
My development complains with strings in Rust fall along two general axes:
- The distinction between string types is confusing
- There are too many ways to convert between string types
Too many string types
Off the top of my head, I can think of 5 different waysto represent strings, views of strings, or signatures that accept string-y things:
-
&str
for borrowed strings -
String
for owned strings -
&OsStr
for borrowed strings in the OS’s representation -
OsString
for owned strings in the OS’s representation -
AsRef<str>
for signatures where a cheap&str
reference can be made
(I’m aware that the last one isn’t really a string type, but it appears regularly in idiomatic string-handling code.)
As a Rust newbie, the distinctions between these types was deeply confusing, and made it more difficult to understand references (Why is a &String
different from a &str
? Why can’t I create a str
directly? Where the hell am I getting &&str
from?).
Too many ways to convert between strings
Multiple string types and relevant traits beget multiple conversion functions:
-
&str
toString
:String::from()
,to_string()
,to_owned()
,into()
, not counting formatting routes or round-tripping with aVec
or[u8]
-
String
to&str
:as_str()
,as_ref()
,Deref<Target=str>
,&x[..]
- Similar (albeit lossy) methods for
OsStr
andCStr
Most of these routes are equivalent in performance, and the Rust community seems divided on which ones are “right”.
I’ve ended up in the habit of using different ones depending on the context (e.g. into()
to indicate that I’m turning a &str
into a String
so I can return it, to_owned()
to indicate that I’m taking ownership to use the string later on).
Standard library gaps
The Rust standard library has some gaps that make aspects of userspace programming painful:
-
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 thedirs
crate (which is currently archived on GitHub). -
No standard way to expand
~
.std::fs::canonicalize
supports.
and..
, but not~
. Yes, I know this is a duplicate of the above. -
No way to invoke a command through a system shell. Yes, I know that
system(3)
is bad. Yes, I agree that it shouldn’t be the default interface for executing other processes, and should even be quarantined to prevent unintentional use. None of that changes the fact that it’s occasionally usefuland can be implemented more reliably in the standard library than by end developers throwingsh -c
around. -
No standard way to
glob
. It looks like theglob
crate is the semi-official way to do this.
These are admittedly minor gaps, and are all addressed by high-quality crates. But they add friction to the development process, friction that’s especially noticeable given how frictionless Rust otherwise tends to be.
Traits
I love trait-based composition. What I don’t love:
-
Is being told that I’m missing
use std::io::Read
oruse std::io::Write
because I’m calling one of their methods that’s beenimpl
‘d by something I already have in scope. I understand why Rust does it this way but it feels weird, especially in the context of unused imports otherwise being compiler warnings. -
The syntax for implementing traits for traits.
impl<T> for Trait for T where T: OtherTrait
isn’t too bad, but it doesn’t read nearly as naturally asimpl Trait for OtherTrait
would. -
Sometimes
rustc
needs me to addwhere Self: Sized
to my static (i.e., non-self
) trait functions. I still don’t understand why this is sometimes required and sometimes isn’t; I’m sure there’s a decent reason.
Safe indexing without widening
Given a fixed array x = [T; N]
and an index variable i
of type U
such that U::MAX < N
, indexing via x[i]
will always be safe. Despite this, rustc
expects the programmer to explicitly widen i
to usize
:
fn main() { let lookup_table: [u8; 256] = [0_u8; 256]; let index = 5_u8; println!("{}", lookup_table[index]); }
fails with:
error[E0277]: the type `[u8]` cannot be indexed by `u8` --> src/main.rs:4:20 | 4 | println!("{}", lookup_table[index]); | ^^^^^^^^^^^^^^^^^^^ slice indices are of type `usize` or ranges of `usize` | = help: the trait `std::slice::SliceIndex<[u8]>` is not implemented for `u8` = note: required because of the requirements on the impl of `std::ops::Index<u8>` for `[u8]`
Understandable, but requires that the programmer either use as usize
everywhere they plan on indexing (verbose, and masks the intent behind the index being a u8
) or that they make index
itself into a usize
(also masks the intent, and makes it easier to do arithmetic that’ll eventually be out-of-bounds).
Bonus: cargo install
doesn’t, sometimes
I don’t know whether this one’s a bona fide bug or not, but I’m tossing it in since it’s bitten me a few times.
cargo install
apparently doesn’t know how to discover suffixed package versions. For example, if I publish myfakepackage
as version 0.0.1-alpha.0
, cargo install
will report:
$ cargo install myfakepackage error: could not find `myfakepackage` in registry `https://github.com/rust-lang/crates.io-index`
You have to explicitly pass --version
:
$ cargo install myfakepackage --version 0.0.1-alpha.0
Wrapup
I had some other things that I wanted to kvetch about (aliases for core types not supporting traits, the package ecosystem being a little too JS/ npm
-y in style), but I figure that doing so runs the risk of being too negative on a language that I am overwhelmingly happy with.
I still like Ruby five years later, and I’m feeling optimistic about Rust.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK