Value Classes and Universal Traits

Introduction

Value classes are a new mechanism in Scala to avoid allocating runtime objects.
This is accomplished through the definition of new AnyVal subclasses.
They were proposed in SIP-15.
The following shows a very minimal value class definition:

class Wrapper(val underlying: Int) extends AnyVal

It has a single, public val parameter that is the underlying runtime representation.
The type at compile time is Wrapper, but at runtime, the representation is an Int.
A value class can define defs, but no vals, vars, or nested traitss, classes or objects:

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,

The remaining sections of this documentation show use cases, details on when allocations do and do not occur, and concrete examples of limitations of value classes.

Extension methods

One use case for value classes is to combine them with implicit classes (SIP-13) for allocation-free extension methods. Using an implicit class provides a more convenient syntax for defining extension methods, while value classes remove the runtime overhead. A good example is the RichInt class in the standard library. RichInt extends the Int type with several methods. Because it is a value class, an instance of RichInt doesn’t need to be created when using RichInt methods.

The following fragment of RichInt shows how it extends Int to allow the expression 3.toHexString:

At runtime, this expression 3.toHexString is optimised to the equivalent of a method call on a static object
(RichInt$.MODULE$.extension$toHexString(3)), rather than a method call on a newly instantiated object.

Correctness

Another use case for value classes is to get the type safety of a data type without the runtime allocation overhead.
For example, a fragment of a data type that represents a distance might look like:

then allocations would not be necessary.
Another instance of this rule is when a value class is used as a type argument.
For example, the actual Meter instance must be created for even a call to identity:

def identity[T](t: T): T = t
identity(Meter(5.0))

Another situation where an allocation is necessary is when assigning to an array, even if it is an array of that value class.
For example,

val m = Meter(5.0)
val array = Array[Meter](m)

The array here contains actual Meter instances and not just the underlying double primitives.

Limitations

Value classes currently have several limitations, in part because the JVM does not natively support the concept of value classes.
Full details on the implementation of value classes and their limitations may be found in SIP-15.

Summary of Limitations

A value class …

… must have only a primary constructor with exactly one public, val parameter whose type is not a value class. (From Scala 2.11.0, the parameter may be non-public.)

… may not have specialized type parameters.

… may not have nested or local classes, traits, or objects

… may not define a equals or hashCode method.

… must be a top-level class or a member of a statically accessible object

… can only have defs as members. In particular, it cannot have lazy vals, vars, or vals as members.

… cannot be extended by another class.

Examples of Limitations

This section provides many concrete consequences of these limitations not already described in the necessary allocations section.

The second error messages shows that although the final modifier is not explicitly specified for a value class, it is assumed.

Another limitation that is a result of supporting only one parameter to a class is that a value class must be top-level or a member of a statically accessible object.
This is because a nested value class would require a second parameter that references the enclosing class.
So, this is not allowed: