6

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 has defs 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?

oxbow_lakes
  • 133,303
  • 56
  • 317
  • 449

1 Answers1

3

Am I correct?

No, we can see it if we emit the final compilation output with -Xprint:jvm:

<synthetic> object F$Wrapper extends Object {
  final def print$extension($this: Int): Unit = 
    scala.Predef.println(new com.testing.F$Wrapper($this));

This is due to the fact println has a type signature requiring Any, 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:

val w: Int = 3;
F$Wrapper.print$extension(w)

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 unwrap Wrapper:

<synthetic> object F$Wrapper extends Object {
  final def print$extension($this: Int): Unit = 
    scala.Predef.println(scala.Int.box($this));

And the call site now looks like this:

val w: Int = 3;
com.testing.F$Wrapper.print$extension(w)

This is valid for both of your examples now, as there is no need for any dynamic dispatch on the created interface.

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • Interesting. I'd actually not really meant to specifically consider the fact it was a call to `print(this)`. Presumably had I chosen `println(underlying)` no instantiation would have been necessary? – oxbow_lakes Nov 06 '17 at 16:28
  • 1
    @oxbow_lakes I was wondering if you intentionally wanted `this` or not. I'll update the answer. – Yuval Itzchakov Nov 06 '17 at 16:28
  • But it causes a call to `Int.box`? Pfft! – oxbow_lakes Nov 06 '17 at 16:32
  • @oxbow_lakes `$this` now refers to the local value `$this: Int` in the method signature, not the `this` pointer. No allocations necessary now. – Yuval Itzchakov Nov 06 '17 at 16:34
  • But your impl of `print$extension` is `scala.Predef.println(scala.Int.box($this));` – oxbow_lakes Nov 06 '17 at 16:35
  • So the `Int` gets boxed into a `java.lang.Integer` – oxbow_lakes Nov 06 '17 at 16:36
  • @oxbow_lakes `print$extension($this: Int)`. The only boxing is because we need an `Integer` instance for `println`, not an `Int`. – Yuval Itzchakov Nov 06 '17 at 16:36
  • @oxbow_lakes Yes, it does, but no allocation of `Wrapper`. This is a limitation of `println`, again since it needs `Any`. – Yuval Itzchakov Nov 06 '17 at 16:37
  • "As long as we reference this inside or outside the value class, we're trapped." I think this is wrong (or I don't understand what you mean). The problem is that here "a value class is treated as another type" (namely, a `Wrapper` as an `Any`); whether it's `this` or not, doesn't matter. And passing `this` to a method accepting a `Wrapper` is fine. – Alexey Romanov Nov 07 '17 at 06:40
  • @Alexey Romanov `this` is problematic in the context of `println` because of `Any`, is what I meant. I'll clarify. – Yuval Itzchakov Nov 07 '17 at 06:42
  • But why does `Int` need to be boxed to be considered an `Any`? `Int <: AnyVal <: Any` – oxbow_lakes Nov 07 '17 at 11:08
  • I've asked this separately here: https://stackoverflow.com/questions/47156541/why-does-scalac-need-to-box-an-int-in-a-method-expecting-an-any – oxbow_lakes Nov 07 '17 at 11:21
  • Doesn't `def print(): Unit = println(underlying.toString)` work to stop the wrapping of `underlying`? IOW, doesn't the use of `toString` enables the compiler to see the "intention" and make the static call as it does in other similar places? – chaotic3quilibrium May 30 '19 at 07:53