Skip to content

Instantly share code, notes, and snippets.

@brendanzab
Last active July 19, 2023 04:28
Show Gist options
  • Star 69 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save brendanzab/d41c3ae485d66c07178749eaeeb9e5f7 to your computer and use it in GitHub Desktop.
Save brendanzab/d41c3ae485d66c07178749eaeeb9e5f7 to your computer and use it in GitHub Desktop.
My personal list of Rust grievances (September 2021)

September 2022:

This has spread to a far wider audience than I had anticipated - probably my fault for using a title that is in hindsight catnip for link aggregators. I wrote this back in 2021 just as a bunch of personal thoughts of my experiences using Rust over the years (not always well thought through), and don't intend on trying to push them further, outside of personal experiments and projects.

Managing a living language is challenging and difficult work, and I am grateful for all the hard work that the Rust community and contributors put in given the difficult constraints they work within. Many of the things I listed below are not new, and there's been plenty of difficult discussions about many of them over the years, and some are being worked on or postponed, or rejected for various good reasons. For more thoughts, please see my comment below.

My personal list of Rust grievances (September 2021)

I love Rust and I have used it daily for many years as my go-to language, but I do have my own list of grievances that give me enough itches to contemplate playing with my own language ideas! Esteban Kuber's tweet inspired me to compile these into a list, even if I don't consider myself a ‘hater’!

Do note that Rust was a product of it's time, and was under many constraints in during its development, and I don't begrudge the choices that were made. Many of these things I only really recognise myself in hindsight!

  • Many sub-languages to learn, many with different syntaxes and semantics. For example:
    • the expression language
      • unsafe runtime language
      • safe runtime language
      • compile time language
    • the type language
    • the trait language
    • the macro language
    • the attribute language
  • Language is designed in a monolithic fashion, as opposed to elaborating to a simple, verifiable core language with roots in type theory.
  • NLL has access to tricks that are not expressible in type system, making it impossible to factor out code in some cases.
  • Complicated, mutually recursive modules that make incremental compilation hard.
  • A large menagerie of traits that could point towards a lack of polymorphism:
    • function types: Fn, FnMut, FnOnce
    • conversions: From, TryFrom, Into, TryInto, As, AsRef, AsMut
  • Traits are hard to extend after the fact, and you can't break them into smaller parts in a backwards-compatible way.
  • Traits bias the Self parameter, which can make some multi-parameter traits rather odd.
  • No way of defining abstract types that conform to a given interface. You can use traits, but these do not support abstract associated types, and require a Self type. Supporting this kind of feature is now difficult due to the complexity of traits (Haskell also struggles here).
  • Trait objects are a bit lackluster
    • Complicated rules around object-safety.
    • Associated types are incompatible with trait objects.
    • Not allowed to combine trait objects
    • Could this all have been achieved with better support for existential types?
  • Marker traits (like Send and Sync) are kind of weird? I don't fully understand the complexities behind them, but IIRC have weird properties like leaking through impl trait. I've also heard it's hard/impossible add more of them in the future.
  • Conflation of mut for unique references and for 'unfrozen' local variables.
  • Specialisation breaks parametricity. This is not necessarily a bad thing imo, but it's impossible to choose if type parameters are parametric or not.
  • Silly gripes about syntax:
    • Angle-brackets (<, >) for generics is a bit of an eye-sore in complicated types.
    • Records use : in their literal form, which is overloaded with type ascription. This is also annoying in cases where you might want to move a field into a let binding.
    • No space before the colon in type annotations in the default formatting.
    • Complexity in the grammar around:
      • allowing semicolons to be omitted after some expressions, like match, if/else, and if.
      • leaving off commas after blocks in match arms
    • match expressions syntax leads to lots of nested indentation
  • Async/await splits the ecosystem: having access to effect polymorphism would have been nice (see the work on typed effect systems, like what Multicore OCaml is working towards).
  • Type aliases are transparent (as opposed to abstract) by default, exposing their definitions publicly.
  • Type aliases are leaky, and can expose implementation details via definitional equality. There are some lints to catch this kind of thing, but they often fail to fire.
  • Hard to keep track of whether functions are panic-safe or not.
  • While using allocators like arenas is possible through the use of crates, Rust's standard library lacks much in the way of support for this style of programming, and most libraries are not implemented with support for it, and the ones that do are often incompatible with each other.
  • IO, file system, and other effect-based libraries are not capability-safe.
  • Poor sandboxing/security for procedural macros (see point on capability-based security). Would be great if procedural macros would be only able to use safe Rust, with stricter requirements on what capabilities they have access to.
  • Lack of support for typed, interactive programming through the use of hole-driven development (As seen in Haskell, Purescript, Idris, Agda, etc). todo!() gets you some of the way there, but it's not possible to use it in places like types and patterns to express partially complete programs. Granted, this could be less useful in the presence of impurity (due to less precise types).
  • You can't run broken Rust programs (translating compile time errors to runtime errors).
  • It might have been better to call unsafe blocks trusted blocks.
  • Lack of visibility of the 'trusted' parts of code a library/code unit is taking on. Less in the sense of naming/shaming, and more making it easier to get visibility and double-check.
  • No support for using something like separation logic within Rust itself to verify that unsafe code upholds the invariants that the safe language expects.
  • Confusion about whether to use kebab-case or snake_case for crate names I now lean to the former, but it's impossible to import snake case crates using kebab case, leading to an ugly mix in my Cargo.toml file.
  • Cargo.toml is capitalised, unlike most other development files.
@tsm-x
Copy link

tsm-x commented Sep 17, 2022

And sometimes it's faster to make copies of immutable things to get them into multiple cache lines so that there is reduced contention rather than zero-copy oneself into a global, single-core hot path.

I don't get this one. If it's immutable, then there are no updates, where's the contention coming from? Isn't read from cache basically free?
Are there any benchmarks that illustrate this claim? My 2 minutes of googling showed no results.

@rlabrecque
Copy link

@tsm-x If you're still interested, search for "Cacheline false sharing". That's likely what the OP is talking about in that case.

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