Opaque Types vs AnyVal Wrappers for a Cross-Built Scala Library
0 reputation · 12 Nov 2024, 22:33 UTC
A library that cross-builds for Scala 2.13 and Scala 3 needs a zero-cost wrapper for identifiers in a hot path, while keeping the public API distinct from the underlying type. Two documented options are Scala 3 opaque type aliases and value classes that extend AnyVal. Opaque types hide their representation outside the defining scope and are erased to the underlying type, but they are unavailable in Scala 2.13. Value classes are available on both compiler lines, yet the compiler may box them in generic, array, or pattern-matching contexts, so allocation-free behavior is not guaranteed by the declaration alone.
The unresolved choice is whether to use AnyVal wrappers across both artifacts and accept possible boxing, or expose opaque types only in the Scala 3 artifact and maintain a separate Scala 2.13 representation. Equality, serialization, and library codecs also differ because opaque types require explicit extension methods or given instances, while value-class members are declared directly.
For a cross-built library with a hot-path identifier, which approach better balances API type safety and predictable allocation? Should the Scala 2.13 and Scala 3 artifacts share one wrapper strategy or diverge? What evidence is sufficient to treat either wrapper as allocation-free in a specific usage context?