Traits are a local maximum

Traits in languages like Rust and Haskell are praised for giving a single, globally consistent implementation of behaviors (e.g., hashing or ordering) for each type–trait pair, but this coherence comes at the cost of flexibility, especially when trying to extend third‑party types with third‑party traits (“orphan” implementations). Commenters contrast this with alternatives such as ML-style named modules or Scala-style implicits, which allow more local or overlapping implementations but risk incoherent behavior and subtle correctness bugs, particularly across libraries. Many see Rust’s strict orphan rules and coherence guarantees as a pragmatic local optimum, with workarounds like newtypes, glue crates, or explicit passing of “trait-like” values used to manage the remaining pain points.

Title and language nitpicks

  • Several comments note that “maxima” is plural and “maximum” is singular, and that the article’s title is grammatically off.
  • Some discussion on stylistic choices in the post (fancy verbs, “proselytize”), with mixed views: some like rich vocabulary, others say good technical writing should feel simple and accessible.

Traits, type classes, ML modules, implicits

  • Core comparison: Rust/Haskell traits/typeclasses vs ML-style modules vs implicits.
  • Traits: instances are unnamed and resolved from (type, trait), which is ergonomic but forces coherence.
  • ML modules: implementations are named; more annotation, but clearer and more explicit.
  • Implicits (as in the article) are framed as “local coherence”: instance selection by lexical proximity, trading off global correctness guarantees for flexibility.

Coherence, orphan implementations, and correctness

  • Strong focus on the “orphan rule” (no impl of foreign trait for foreign type) and global coherence.
  • Many argue coherence is essential for correctness: e.g., hashing or set ordering must be consistent across crates, or you can corrupt collections.
  • Multiple proposals surface:
    • Import/export of orphan impls explicitly.
    • Restrict orphans to binaries or special “glue” crates.
    • Allow “impl if not already implemented” / warnings instead of errors.
  • Pushback: these lead to unpredictable behavior when dependencies change; people value Rust’s lack of linker-style surprises.

Workarounds and alternative designs

  • Newtypes are widely discussed as the sanctioned workaround; seen as correct but often boilerplate-heavy. Ideas: better ergonomics (e.g., Deref, deriving) without semantic surprises.
  • Other ecosystem examples: Haskell’s orphans + warnings, Julia’s “type piracy,” Nim’s UFCS and mixins, dependently-typed implicits as explicit parameters.
  • Some suggest traits as just sets of functions (no impls), relying on normal name resolution; critics note this still doesn’t solve sharing implementations, only signatures.

Ad hoc polymorphism remains hard

  • Several commenters describe ad hoc polymorphism (traits/typeclasses/implicits) as an unsolved tradeoff space rather than something with a clear “right” answer.
  • General sentiment: Rust’s current trait system is a “local maximum” many are happy with, but there’s clear demand for more ergonomic cross-crate “glue,” especially for things like serialization and database traits.