Scala LanguageVar, Val, and Def


Remarks

As val are semantically static, they are initialized "in-place" wherever they appear in the code. This can produce surprising and undesirable behavior when used in abstract classes and traits.

For example, let's say we would like to make a trait called PlusOne that defines an increment operation on a wrapped Int. Since Ints are immutable, the value plus one is known at initialization and will never be changed afterwards, so semantically it's a val. However, defining it this way will produce an unexpected result.

trait PlusOne {
    val i:Int

    val incr = i + 1
}

class IntWrapper(val i: Int) extends PlusOne

No matter what value i you construct IntWrapper with, calling .incr on the returned object will always return 1. This is because the val incr is initialized in the trait, before the extending class, and at that time i only has the default value of 0. (In other conditions, it might be populated with Nil, null, or a similar default.)

The general rule, then, is to avoid using val on any value that depends on an abstract field. Instead, use lazy val, which does not evaluate until it is needed, or def, which evaluates every time it is called. Note however that if the lazy val is forced to evaluate by a val before initialization completes, the same error will occur.

A fiddle (written in Scala-Js, but the same behavior applies) can be found here.