In the specification of value classes, it says:
A value class can only extend universal traits and cannot be extended itself. A universal trait is a trait that extends
Any
, only hasdef
s as members, and does no initialization. Universal traits allow basic inheritance of methods for value classes, but they incur the overhead of allocation. For example
trait Printable extends Any {
def print(): Unit = println(this)
}
class Wrapper(val underlying: Int) extends AnyVal with Printable
val w = new Wrapper(3)
w.print() // actually requires instantiating a Wrapper instance
First Question
Now, I would take this to mean that the following (probably) does not require instantiation:
trait Marker extends Any
class Wrapper(val underlying: Int) extends AnyVal with Marker {
def print(): Unit = println(this) //unrelated to Marker
}
val w = new Wrapper(3)
w.print() //probably no instantiation as print is unrelated to Marker
Am I correct?
Second Question
And I would think there is an even chance as to whether this requires instantiation or not:
trait Printable extends Any {
def print(): Unit //no implementation
}
class Wrapper(val underlying: Int) extends AnyVal with Printable {
override def print() = println(this) //moved impl to value class
}
val w = new Wrapper(3)
w.print() // possibly requires instantiation
On the balance of probability, I would also think that no instantiation would be needed - am I correct?
Edit
I'd not thought about the exact implementation of print()
in the example:
def print(): Unit = println(this)
Let's say that I used the following instead:
def print(): Unit = println(underlying)
Would these cause instantiations?
No, we can see it if we emit the final compilation output with
-Xprint:jvm
:This is due to the fact
println
has a type signature requiringAny
, so we're shooting ourselves in the foot here since we're effectively "treating the value class ttpe as another type".Although the call is dispatched to the static method call:
We're still incurring the allocation inside
print$extension
.If we stray away from using
Wrapper.this
, then your first assumption is indeed correct and we can see the compiler happily unwrapWrapper
:And the call site now looks like this:
This is valid for both of your examples now, as there is no need for any dynamic dispatch on the created interface.