Skip to content

Instantly share code, notes, and snippets.

@overthink
Last active May 29, 2018 13:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save overthink/e6a4a9264110190b7e00f1a3880599f1 to your computer and use it in GitHub Desktop.
Save overthink/e6a4a9264110190b7e00f1a3880599f1 to your computer and use it in GitHub Desktop.
Demo of why vals in traits are "bad" (aka you have to be very careful)
The issue is that the superclass gets instantiated first, and if it has an eager val that
calls an abstract def (or abstract lazy val, if such a thing is possible) it will capture
the not-yet-fully-initialized value of that val in the subclass (which is usually null).
scala> trait T { def f: String; val callsF = f } // *val* `callsF` calls abstract def `f` -- risky!
defined trait T
scala> object X extends T { val f = "foooo" } // `f` is implemented with a strict val -- bomb is set
defined object X
scala> X.callsF
res0: String = null // unexpectedly, X.callsF returns null instead of "foooo"
scala> object Y extends T { def f = "baaar" } // implement `f` with a def -- all good
defined object Y
scala> Y.callsF
res2: String = baaar // as we'd hope
So you either have an out-of-band agreement that all subclasses must implement the
trait with only `def`s, or you only use defs (and lazy vals if you must) in your trait,
and the implementors are free to do whatever. I like the latter option personally.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment