'extends AnyVal' is good for extension methods and…that's it: Scala ritual

My post “The High Cost of AnyVal subclasses” covers most of the ground of how, in many practical cases, extends AnyVal actually allocates more instances of the class, rather than fewer, compared to a normal class, with many bytecode examples to demonstrate.

There is one appropriate use case for extends AnyVal: writing extension methods.

// the new way
implicit final class Hello(private val self: Int) extends AnyVal {
  def hello = self + 42
}

// the old-fashioned way (often more flexible)
final class Goodbye(private val self: Int) extends AnyVal {
  def goodBye = self - 42
}
implicit def Goodbye(self: Int): Goodbye = new Goodbye(self)

// trying them out
0.hello.goodBye

The last line desugars as

Goodbye(Hello(0).hello).goodBye

At the bytecode level, both Goodbye and Hello merely return their int argument. The hello method compiles as

  public final int hello$extension(int);
    Code:
       0: iload_1
       1: bipush        42
       3: iadd
       4: ireturn

And it is similar for the goodBye method.

Informally, the key to a good use case for extends AnyVal is lifetime. In the desugared code, a Hello is created, then immediately discarded in favor of the call to its hello method, whose result is not an AnyVal subclass. So if you use extends AnyVal only in this way, you will not fall into unexpected allocation traps.

Otherwise, you almost certainly will end up allocating in unexpected ways. If you want a true unboxed newtype, use the existential technique I describe in the original “High Cost” article, or simply use Scalaz tags.

Tested with Scala 2.12.11.

7 Likes