From 890996bf6110df9e91cbd7cdbde5ff3f425afc9f Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Sat, 23 Nov 2013 03:56:30 +0100 Subject: [PATCH 1/3] Save entry from paulp/scala-faq Copy-paste the source of https://github.com/paulp/scala-faq/wiki/Initialization-Order together with the template of: https://raw.github.com/scala/scala.github.com/master/tutorials/FAQ/finding-implicits.md No other changes in this commit. --- tutorials/FAQ/initialization-order.md | 161 ++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 tutorials/FAQ/initialization-order.md diff --git a/tutorials/FAQ/initialization-order.md b/tutorials/FAQ/initialization-order.md new file mode 100644 index 0000000000..4dc2c97100 --- /dev/null +++ b/tutorials/FAQ/initialization-order.md @@ -0,0 +1,161 @@ +--- +layout: overview-large +title: Why is my abstract or overridden val null? + +disqus: true + +partof: FAQ +num: 9 +--- +Consider the following. +```scala +abstract class A { + val x1: String + val x2: String = "mom" + + println("A: " + x1 + ", " + x2) +} +class B extends A { + val x1: String = "hello" + + println("B: " + x1 + ", " + x2) +} +class C extends B { + override val x2: String = "dad" + + println("C: " + x1 + ", " + x2) +} +// scala> new C +// A: null, null +// B: hello, null +// C: hello, dad +``` + +A 'strict' or 'eager' val is one which is not marked lazy. + +In the absence of "early definitions" (see below), initialization of strict vals is done in the following order. + +1. Superclasses are fully initialized before subclasses. +2. Otherwise, in declaration order. + +Naturally when a val is overridden, it is not initialized more than once. So though x2 in the above example is seemingly defined at every point, this is not the case: an overridden val will appear to be null during the construction of superclasses, as will an abstract val. + +There is a compiler flag which can be useful for identifying this situation: + +**-Xcheckinit**: Add runtime check to field accessors. + +It is inadvisable to use this flag outside of testing. It adds significantly to the code size by putting a wrapper around all potentially uninitialized field accesses: the wrapper will throw an exception rather than allow a null (or 0/false in the case of primitive types) to silently appear. Note also that this adds a *runtime* check: it can only tell you anything about code paths which you exercise with it in place. + +Using it on the opening example: +```bash +% scalac -Xcheckinit a.scala +% scala -e 'new C' +scala.UninitializedFieldError: Uninitialized field: a.scala: 13 + at C.x2(a.scala:13) + at A.(a.scala:5) + at B.(a.scala:7) + at C.(a.scala:12) +``` + +Approaches for avoiding null values include: + +#### Use lazy vals. #### + +```scala +abstract class A { + val x1: String + lazy val x2: String = "mom" + + println("A: " + x1 + ", " + x2) +} +class B extends A { + lazy val x1: String = "hello" + + println("B: " + x1 + ", " + x2) +} +class C extends B { + override lazy val x2: String = "dad" + + println("C: " + x1 + ", " + x2) +} +// scala> new C +// A: hello, dad +// B: hello, dad +// C: hello, dad +``` + +Usually the best answer. Unfortunately you cannot declare an abstract lazy val. If that is what you're after, your options include: + +1. Declare an abstract strict val, and hope subclasses will implement it as a lazy val or with an early definition. If they do not, it will appear to be uninitialized at some points during construction. +2. Declare an abstract def, and hope subclasses will implement it as a lazy val. If they do not, it will be re-evaluated on every access. +3. Declare a concrete lazy val which throws an exception, and hope subclasses override it. If they do not, it will... throw an exception. + +An exception during initialization of a lazy val will cause the right hand side to be re-evaluated on the next access: see SLS 5.2. + +Note that using multiple lazy vals creates a new risk: cycles among lazy vals can result in a stack overflow on first access. + +#### Use early definitions. #### +```scala +abstract class A { + val x1: String + val x2: String = "mom" + + println("A: " + x1 + ", " + x2) +} +class B extends { + val x1: String = "hello" +} with A { + println("B: " + x1 + ", " + x2) +} +class C extends { + override val x2: String = "dad" +} with B { + println("C: " + x1 + ", " + x2) +} +// scala> new C +// A: hello, dad +// B: hello, dad +// C: hello, dad +``` + +Early definitions are a bit unwieldy, there are limitations as to what can appear and what can be referenced in an early definitions block, and they don't compose as well as lazy vals: but if a lazy val is undesirable, they present another option. They are specified in SLS 5.1.6. + +#### Use constant value definitions. #### +```scala +abstract class A { + val x1: String + val x2: String = "mom" + + println("A: " + x1 + ", " + x2) +} +class B extends A { + val x1: String = "hello" + final val x3 = "goodbye" + + println("B: " + x1 + ", " + x2) +} +class C extends B { + override val x2: String = "dad" + + println("C: " + x1 + ", " + x2) +} +abstract class D { + val c: C + val x3 = c.x3 // no exceptions! + println("D: " + c + " but " + x3) +} +class E extends D { + val c = new C + println(s"E: ${c.x1}, ${c.x2}, and $x3...") +} +//scala> new E +//D: null but goodbye +//A: null, null +//B: hello, null +//C: hello, dad +//E: hello, dad, and goodbye... +``` +Sometimes all you need from an interface is a compile-time constant. + +Constant values are stricter than strict and earlier than early definitions and have even more limitations, +as they must be constants. They are specified in SLS 4.1. From 9f9804e8e7d7f12a0207c7b3743eb844dfe4ba15 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Sat, 23 Nov 2013 04:00:41 +0100 Subject: [PATCH 2/3] Fix sectioning --- tutorials/FAQ/initialization-order.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tutorials/FAQ/initialization-order.md b/tutorials/FAQ/initialization-order.md index 4dc2c97100..01e26f552c 100644 --- a/tutorials/FAQ/initialization-order.md +++ b/tutorials/FAQ/initialization-order.md @@ -57,9 +57,11 @@ scala.UninitializedFieldError: Uninitialized field: a.scala: 13 at C.(a.scala:12) ``` +### Solutions ### + Approaches for avoiding null values include: -#### Use lazy vals. #### +#### Use lazy vals #### ```scala abstract class A { @@ -94,7 +96,7 @@ An exception during initialization of a lazy val will cause the right hand side Note that using multiple lazy vals creates a new risk: cycles among lazy vals can result in a stack overflow on first access. -#### Use early definitions. #### +#### Use early definitions #### ```scala abstract class A { val x1: String @@ -120,7 +122,7 @@ class C extends { Early definitions are a bit unwieldy, there are limitations as to what can appear and what can be referenced in an early definitions block, and they don't compose as well as lazy vals: but if a lazy val is undesirable, they present another option. They are specified in SLS 5.1.6. -#### Use constant value definitions. #### +#### Use constant value definitions #### ```scala abstract class A { val x1: String From b38c07ab735d12df16e07b42b1685a1a8ea86a91 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Sat, 23 Nov 2013 04:11:33 +0100 Subject: [PATCH 3/3] State problem more explicitly I feel this is the required editing to make the point explicit. --- tutorials/FAQ/initialization-order.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/tutorials/FAQ/initialization-order.md b/tutorials/FAQ/initialization-order.md index 01e26f552c..184c295528 100644 --- a/tutorials/FAQ/initialization-order.md +++ b/tutorials/FAQ/initialization-order.md @@ -7,7 +7,10 @@ disqus: true partof: FAQ num: 9 --- -Consider the following. + +## Example +To understand the problem, let's pick the following concrete example. + ```scala abstract class A { val x1: String @@ -25,12 +28,18 @@ class C extends B { println("C: " + x1 + ", " + x2) } -// scala> new C -// A: null, null -// B: hello, null -// C: hello, dad ``` +Let's observe the initialization order through the Scala REPL: +``` +scala> new C +A: null, null +B: hello, null +C: hello, dad +``` + +Only when we get to the constructor of `C` are both `x1` and `x2` initialized. Therefore, constructors of `A` and `B` risk running into `NullPointerException`s. +## Explanation A 'strict' or 'eager' val is one which is not marked lazy. In the absence of "early definitions" (see below), initialization of strict vals is done in the following order.