Skip to content

Instantly share code, notes, and snippets.

@warpfork
Last active July 17, 2024 10:38
Show Gist options
  • Save warpfork/5d2bbfd0d6430f7b7af42973f8529d45 to your computer and use it in GitHub Desktop.
Save warpfork/5d2bbfd0d6430f7b7af42973f8529d45 to your computer and use it in GitHub Desktop.
Rust drives me bonkers, 2024

I'm not sure if this is a good list of issues I have with Rust.

Maybe some of them can be addressed. Maybe even immediately, in ways I don't yet know.

But the theme of the file below is that I started very enamored with rust... and then my practical experience with it after about a hundred hours was that I had staggeringly low productivity, because of an endless series of papercuts.

I'm no longer convinced rust is a slam dunk choice. (But I still wish it was.)

Fwiw, the things I value are:

  • a compiled language (guide me to correct code!)
  • a good type system (guide me to correct code!!! In particular: variants/sum-types, please!)
  • a good type system (for expressiveness. Bicycle for the mind when developing is important.)
  • a good library ecosystem (I don't want to reinvent every wheel, even if it's fun!)
  • a good shipping story (if I have to learn about libc issues, I am wasting time in a horrible way!)
  • a decent dev velocity (testing should be nice; compiler error messages should be helpful; big topic...)
  • and good performance (it's a stretch goal for some projects, but I like things that don't have upper limits.)

Rust does pretty okay on most of these. It's purely the dev velocity that's so mindbogglingly negative that I'm currently shellshocked by how vastly far outside of my expectations it fell.

Anyway, without further ado or intro... the next file is my raw notes.

Rust: Can we hack here, happily?

Positive factors, Driving me towards Rust

  • ability to link the product into other languages
  • hopes relating to wasm
  • pretty good type system (the basics: sum, product, generics)
  • immutablity!
  • errors as values, and even better, as sum types!
    • ...although admittedly the use of sum types is community partial.
  • library quality in general is very high
    • display-parse
    • clap
    • serde
    • indicatif
    • in general, when i find stuff, it's good and it's efficient and it integrates with other things well.
  • borrowing is in fact amazing.
    • really amazing. I've wanted to be able to have this kind of explicitness about memory origination and lifetime control in other languages for ages. It's fantastic. Of all the things in rust that other folks complain about, this one, I don't get. Borrowing is amazing. And the borrow checker is quite good. And the feedback the compiler gives about it is (usually) downright fantastic. Borrowing rocks.
  • macros
    • printf as a compile time macro is the right way!
    • the syn and quote libraries are very good.
    • I do like being able to reach for this power level when it's justified... And having errors from the macro usage immediate get underlined in an IDE is very nice.
  • the way traits and parameter+return type polymorphism and generic parameter constraints all play together to let you accept parameters with types like <S: AsRef<str>>(s S) -- meaning, accept anything that I can find a route to turn into a reference to a string, in this example -- is very excellent and allows for some fantastic ergonomics to be created.

Frustrations over time: experiences now driving me away from Rust:

(In no particular order.)

  • async is infectious madness
    • hard to put a finger on exactly where this goes off into "too much", because it's legitimately hard problem, but it gets eyebrow raising.
  • compile times ARE slow. I'm sorry, but it's A LOT.
  • the strings situation. But probably not annoyed at the part you expect.
    • the strict utf8 stuff is just bonkers.
  • macros
    • the way they can turn the syntax parsing basically off... Is very unpleasant. Admittedly not every macro does this, but I've encountered plenty that manage to completely confuse and derange rustfmt.
  • rustfmt
    • i actually cannot overstate how critically bad this is. It's a diff churn factory and a syntactic noise factory to a degree that I find honestly seriously problematic and noticeably detrimental to both immediate productivity flow and to maintenance over time (git blame becomes almost unusable!).
    • rustfmt's fetishization of line length limits combined with total disregard of line breaks placed by the programmer produce the majority of the issues.
    • I have seen rustfmt put a single "{" on a line, Allman-style, when the line before it is juuuust the wrong magic length threshhold. Breathtaking.
    • I have seen rustfmt collapse lines but forget to process commas, so I get a ,); in the result.
    • I have seen many git diffs that become dozens of lines instead of single lines, because a tiny (sometimes even single character) change causes rustfmt to produce a giant reflowing.
    • there are no less than 10 (!) open issues on rustfmt right now about it removing comments. (?!?!)
    • I could continue with examples of malfeasance for quite a while. I'd rather not. It's actively disruptive and let's leave it at that.
  • the amount of sheer ceremony around having code in multiple files.
    • having to say "mod foo" for every "foo.rs" file is just pure ceremony with no useful purpose. It has only ever served to annoy and distract me.
  • the relationship of files and directories to module names defaults in all the wrong directions.
    • the difference between "foo/mod.rs" and "foo.rs" -- I don't care if it's legacy -- is silly.
    • that one starts with a "foo.rs" file and switches to "foo/mod.rs" later in order to have deeper directories... is yet another situation where very normal evolution of code in rust forces you to have large changes in source that break git blame, or produce and exacerbate the potential for merge conflicts.
    • the fact that you are almost assured to have many, many "mod.rs" files, is unhelpful.
    • oh, I could've just stayed with "foo.rs" while adding "foo/bar.rs"? Great -- I'm so glad that I got to choose my own adventure there, and some of the choices were bad.
  • testing: the output. It's remarkably bad.
    • five lines minimum for a single test.
    • that repeats, and is in no way collated, when you have tests in more than one module.
    • this huge output happens even when you specified a specific test function name, and an entire output block is for a set of tests where 100% of them are skipped!
  • testing: the cli flags.
    • getting output shown requires -- -- to appear. Four dashes and a space. That's absurd. There's some more words after that too, and I don't remember them. "show_output"? Underscore or dash? Or was it "no-capture"? I don't know. I don't want to remember this shibboleth. One sane option for this flag would've been "-v", but whatever it is, it shouldn't be longer than that!
  • testing: the focus selection in the CLI.
    • the use of module paths, which are not actual paths and thus are a pain to tab-complete, is just taking a hammer to your own knee for no reason.
  • testing: the magic files.
    • Rust's concept of integration test files is just insanely magic. I want every neuron I've ever spent on this back.
    • the common folder, and the function named "setup", double-tripple-insano magic! We have macros in rust... We didn't need this insane and illegible special case thing. It's both another special case to brute-force remember, and another thing that you have absolutely no chance of discovering is in play at all by reading the source of the project in front of you.
    • (and after all that, it's lower power than anything you could do with some well designed macros, too!)
  • compiler error output. Feedback comes in phases and it's almost never relevant-first -- often quite the opposite.
    • all errors with macros are reported first, and completely block to reporting of even completely obvious compile errors outside of macros.
    • warnings that are very low relevance come out at all times. A major example of this are unused import warnings... Which have a nontrivial chance of being used after all, by the time you fix all the other more serious macro and compile errors!
    • lifetime analysis errors come last of all, after all other compiler and macro errors... And yet are quite likely going to derail your entire plan if you're doing any kind of interesting refactor. So it's a shame you don't get a single inkling about any of them until you've completely finished miles of other work that now might well be wasted.
  • impl. The fact that two impl Foo are not equal types... I just honestly don't know what this language feature is for.
    • (I'm willing to be educated on this one! But so far I have only been given a bad time when I see this keyword pop up. And at this rate, I'm pretty likely to do a personal hard abort on rust before I figure it out.)
  • traits modifying the method sets on existing types invisibly when in scope. It's about as bad for legibility as Scala's implicits. Which is to say "very, very bad".
    • there are often "use" statements which I feel obliged to leave a comment on, because otherwise their purpose and effect is entirely undiscoverable from reading of the source.
      • did I mention rustfmt will whimsically move comments on use statements to other, incorrect and not relevant, lines? Rustfmt is so bad I almost lack words to describe the magnitude of catastrophe.
  • features like Into. It's great in that it lets you (sometimes, maybe, probably) say "Do What I Mean" to get data from one type to another. But it's a time bomb for maintainability: if you're refactoring some code using these, or a library you depend on has changed in a way that affects these (which could happen either by changing its own exported instances of an Into trait, or by merely changing args types it accepts to some functuon you've into()'d something as you pass args in!), have fun figuring out what the previous author meant when they tossed this "Do What I Mean" into the code! Maybe it will still be obvious. Maybe not. Good luck.
  • features like AsRef. Anything that permits a function to be called implicitly, without a single character you can point to in the source syntax that says it's happening, is a misfeature, imo.
    • (i realize a good number of languages do this, including c# and ruby and modern JavaScript, with "properties". But I do consistently think it's a mistake in all of them.)
  • one can hide things from docs. Fuck entirely off.
  • the rust toolchain shipping story itself is extremely unnecessarily complicated.
    • if you try to get things installed yourself without rustup, you will find it's remarkably difficult to find all the pieces, and it is also impossible to go from the distributed tarballs to a working system without a very large amount of filesystem move thrashing, and a considerable amount of shellscript to relocate it all. When upstream could've just... Provided a tarball with the files in the right places.
    • meanwhile as you try to reverse engineer this madness, you'll trip over hundreds of people telling you you're an idiot and you should give up and use rustup. (Every one of the people who has done this: fuck off into the sun; your attitude towards this is one of the most despicable attitudes on this earth that I truly cannot understand nor tolerate. You make the world greatly and needlessly worse when you actively clog up the conversation of others in this way.)
    • when all is said and done and the core tools work... You'll still find rust-analyzer doesn't. It needs the sources of the stdlib, which have not yet been provided at all in any of the tarballs you're likely to have found by now. That source? It's actually in... The rustc source. Yes. Not where you expected it, is it. It's wedged in there with hundreds of megabytes, if not gigabytes, of very unrelated things used in building rust itself. But you must download and unpack all of it to find the tiny sliver of it that you actually need. And then you must magically intuit the path where you must copy this into the toolchain install paths, because no one has documented this. Good luck.
  • the need for boilerplate macros is somewhat significant. There's a handful of them that you're pretty much "always" going to be taping on to every type you create.
    • this isn't a critical pain; overall it's one I'm quite willing to ignore. But it's another papercut to add to the stack of papercuts.
    • To give an example of the magnitude of boilerplate, this one's pretty common to my experience: #[derive(Clone, Debug, PartialEq, Eq, Hash, Display)] ... it's... quite a list. "Clone" is the only one of those that I really see as having a compelling reason to be non-default.
    • rustfmt does make this more painful, though, impressively. If you have some derives that are interesting and unusual (and perhaps even deserve a line comment), and a bunch of derives that are pure boilerplate... yeah, rustfmt doesn't care. It's gonna smash them all into one line. This both makes copying and pasting the boilerplate pieces (and seeing if you've forgotten one, at a glance) harder, impacting immediate dev flow... and means you can't document the interesting ones easily, which is just frankly offensive to me.
  • god help you if someone doesn't #[derive(Eq,PartialEq)] on a type.
    • if it's missing, you're just in for pain. assert_eq! and friends in a test? Nope.
    • by contrast: I honestly think golang (and other's) approach of making everything have a built in equality is much, much more useful in almost every case.
    • and by comparison: in other languages (ex: java) that allow custom equality methods, I have similarly absolutely never that I can think of seen that custom equality used For Good. Only for footgun.
    • and even in rust: I have, so far, literally never seen someone actually use the ability to do a more custom equality method. I have only ever seen the default derives used. So the demand for this feature being flexible seems... low. Quite low. Zero, we might say.

Almost none of these downsides would be a hard stop on their own. But collectively, they result in so many and such constant productivity debuffs that it's, to my considerable surprise, making Rust look less habitable than some other (otherwise greatly inferior) languages.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment