ENOSUCHBLOG

Programming, philosophy, pedaling.


Things I hate about Rust

May 20, 2020

Tags: programming, rant

Five years ago, I wrote 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:

  1. The distinction between string types is confusing
  2. 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 ways1 to represent strings, views of strings, or signatures that accept string-y things:

(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:

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:

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:

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.


  1. Not counting CString and &CStr, since those are primarily used in FFI contexts and are understandably different. 

  2. I understand that it’s actually remarkably difficult to reliably get the user’s home directory on POSIX platforms. That doesn’t change the fact that the standard library should attempt to. 

  3. Case in point: CLIs frequently expose hook-points and callbacks where being able to write in shell syntax is useful.