# Nullable Types

Consider a function that sometimes produces “no result.” When this happens, the function doesn’t produce an error per se. Nothing went wrong, there’s just “no answer.”

A good example is retrieving a value from a Map. If the Map doesn’t contain a value for a given key, it can’t give you an answer and returns a null reference to indicate “no value”:

// NullableTypes/NullInMaps.kt
import atomictest.eq

fun main() {
  val map = mapOf(0 to "yes", 1 to "no")
  map[2] eq null
}

Languages like Java allow a result to be either null or a meaningful value. Unfortunately, if you treat null the same way you treat a meaningful value, you get a dramatic failure (In Java, this produces a NullPointerException; in a more primitive language like C, a null pointer can crash the process or even the operating system or machine). The creator of the null reference, Tony Hoare (opens new window), refers to it as “my billion-dollar mistake” (although it has arguably cost much more than that).

One possible solution to this problem is for a language to never allow nulls in the first place, and instead introduce a special “no value” indicator. Kotlin might have done this, except that it must interact with Java, and Java uses nulls.

Kotlin’s solution is arguably the best compromise: types default to non-nullable. However, if something can produce a null result, you must append a question mark to the type name to explicitly tag that result as nullable:

// NullableTypes/NullableTypes.kt
import atomictest.eq

fun main() {
  val s1 = "abc"             // [1]

  // Compile-time error:
  // val s2: String = null   // [2]

  // Nullable definitions:
  val s3: String? = null     // [3]
  val s4: String? = s1       // [4]

  // Compile-time error:
  // val s5: String = s4     // [5]
  val s6 = s4                // [6]

  s1 eq "abc"
  s3 eq null
  s4 eq "abc"
  s6 eq "abc"
}
  • [1] s1 can’t contain a null reference. All the vars and vals we’ve created in the book so far are automatically non-nullable.
  • [2] The error message is: null can not be a value of a non-null type String.
  • [3] To define an identifier that can contain a null reference, you put a ? at the end of the type name. Such an identifier can contain either null or a regular value.
  • [4] Both nulls and regular non-nullable values can be stored in a nullable type.
  • [5] You can’t assign an identifier of a nullable type to an identifier of a non-null type. Kotlin emits: Type mismatch: inferred type is String? but String was expected. Even if the actual value is non-null as in this case (we know it’s "abc"), Kotlin won’t allow it because they are two different types.
  • [6] If you use type inference, Kotlin produces the appropriate type. Here, s6 is nullable because s4 is nullable.

Even though it looks like we just modify an existing type by adding a ? at the end, we’re actually specifying a different type. For example, String and String? are two different types. The String? type forbids the operations in lines [2] and [5], thus guaranteeing that a value of a non-nullable type is never null.

Retrieving a value from a Map using square brackets produces a nullable result, because the underlying Map implementation comes from Java:

// NullableTypes/NullableInMap.kt
import atomictest.eq

fun main() {
  val map = mapOf(0 to "yes", 1 to "no")
  val first: String? = map[0]
  val second: String? = map[2]
  first eq "yes"
  second eq null
}

Why is it important to know that a value can’t be null? Many operations implicitly assume a non-nullable result. For example, calling a member function will fail with an exception if the receiver value is null. In Java such a call will fail with a NullPointerException (often abbreviated NPE). Because almost any value can be null in Java, any function invocation can fail this way. In these cases you must write code to check for null results, or rely on other parts of the code to guard against nulls.

In Kotlin you can’t simply dereference (call a member function or access a member property) a value of a nullable type:

// NullableTypes/Dereference.kt
import atomictest.eq

fun main() {
  val s1: String = "abc"
  val s2: String? = s1

  s1.length eq 3         // [1]
  // Doesn't compile:
  // s2.length           // [2]
}

You can access members of a non-nullable type as in [1]. If you reference members of a nullable type, as in [2], Kotlin emits an error.

Values of most types are stored as references to the objects in memory. That’s the meaning of the term dereference—to access an object, you retrieve its value from memory.

The most straightforward way to ensure that dereferencing a nullable type won’t throw a NullPointerException is to explicitly check that the reference is not null:

// NullableTypes/ExplicitCheck.kt
import atomictest.eq

fun main() {
  val s: String? = "abc"
  if (s != null)
    s.length eq 3
}

After the explicit if-check, Kotlin allows you to dereference a nullable. But writing this if whenever you work with nullable types is too noisy for such a common operation. Kotlin has concise syntax to alleviate this problem, which you’ll learn about in subsequent atoms.

Whenever you create a new class, Kotlin automatically includes nullable and non-nullable types:

// NullableTypes/Amphibian.kt
package nullabletypes

class Amphibian

enum class Species {
  Frog, Toad, Salamander, Caecilian
}

fun main() {
  val a1: Amphibian = Amphibian()
  val a2: Amphibian? = null
  val at1: Species = Species.Toad
  val at2: Species? = null
}

As you can see, we didn’t do anything special to produce the complementary nullable types—they’re available by default.

Exercises and solutions can be found at www.AtomicKotlin.com.