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.