The borrowchecker is what I like the least about Rust
Scope of the criticism
- Many commenters feel the article overstates borrow‑checker flaws and uses contrived examples; others say those examples accurately capture real ergonomic pain on large, evolving codebases.
- Key pain described: small changes to ownership or data layout can force large refactors, making mature Rust code feel rigid.
Disjoint borrows, encapsulation, and function signatures
- The “disjoint fields” complaint (e.g.,
x_mut/y_mutonPoint) is widely argued to be fundamental to Rust’s model, not a missing optimization:- Methods that take
&mut selfare treated as borrowing the whole struct; private fields are not reasoned about individually. - This preserves Rust’s “golden rule”: a function’s signature alone must suffice for typechecking; callers must not depend on function bodies.
- Methods that take
- Workarounds: explicit “split” APIs (e.g.,
fn xy_mut(&mut self) -> (&mut f64, &mut f64)), making fields public, view types / partial‑borrow designs, or future “view”/subset annotations. - Polonius is mentioned: it can fix some lifetime bugs (e.g. a
get_default-style example) but won’t change these fundamental rules.
Borrow checker vs alternatives (GC, indices, Rc/unsafe)
- Some argue: for non–systems domains (e.g. scientific computing), GC languages (Julia, Python, OCaml, Go, Java) give faster iteration with acceptable performance; Rust’s borrow checker is “too much” for their needs.
- Others counter that:
- Rust’s ownership model improves not just memory safety but general correctness and “local reasoning,” especially in large, long‑lived, concurrent systems.
- Refactors in Rust tend to be safer: the compiler becomes a strong gate against subtle bugs.
- Common escape hatches:
- Integer indices into arenas / vectors (often with generational indices) for graphs and back‑references: still memory‑safe in Rust (bounds checks, panics instead of UB) but can reintroduce logical “dangling reference” bugs.
Rc/Arc+RefCell/Mutexto push checking to runtime; orunsafefor custom data structures.- Critics note this partially undercuts the “all safety, zero compromises” narrative.
Graphs, cyclic data, and back‑references
- Many agree Rust is awkward for back‑references and cyclic structures:
- Typical patterns: indices,
Rc/Weak, interior mutability, or unsafe pointer‑based implementations. - Some suggest static analysis over
RefCell::borrowscopes could, in theory, restore more compile‑time guarantees, but this likely requires interprocedural analysis and complex annotations.
- Typical patterns: indices,
Concurrency and “fearless concurrency”
- One side: Rust’s concurrency story (e.g.,
Send,Sync) is inseparable from the borrow checker; giving ownership to another thread is only safe because aliasing is tightly controlled. - Another side: other languages (Pony, Swift) show that static concurrency safety doesn’t require a Rust‑style borrow checker, though Rust’s model and its concurrency rules “rhyme.”
Ergonomics, skill, and culture
- Some see borrow‑checker struggles as mostly a “skill issue” that fades with experience and idiomatic design (functional style, tree‑like ownership).
- Others insist the ergonomic cost is real even for experienced users, especially when ownership has to change late in a project.
- Several point out that Rust’s culture—talks and libraries focused on correctness—is itself a major benefit; the borrow checker attracted that community.
- A recurring view: the borrow checker is not what people most enjoy about Rust day‑to‑day (Cargo, pattern matching, ADTs, ecosystem rank higher), but it is what made Rust distinctive and successful.