No NPEs in Scala - Option type and NotNull trait

2010-03-04 by mira

Scalas solution to avoid null pointer exceptions has two parts; Option type and NotNull trait.

The Option type comes into place where its legal not to have a concrete object value. The advantage over using null instead - as one might do in Java - is that you have the distinction between “value might be null” and “value is never null” baked into the type system.

scala> val pets = Map("Michael" -> "Dog", "Fred" -> "Cat")

pets: scala.collection.immutable.Map[java.lang.String,java.lang.String]
  = Map(Michael -> Dog, Fred -> Cat)

scala> pets get "Michael"
res0: Option[java.lang.String] = Some(Dog)

scala> pets get "Jim"
res1: Option[java.lang.String] = None

That way, the compiler helps you not to get NPEs by forcing you to look inside the Option to get to the value itself.

scala> pets get "Michael" match {

     | case Some(petName) => petName
     | case None => "NO_PET"
     | }
res2: java.lang.String = Dog

In places where a value is mandatory, one can mixin the NotNull trait. Doing so, the compiler will check every assignment to this type and complain in places where null is assigned.

scala> case class Pet(name: String) extends NotNull
defined class Pet

scala> val lia = Pet("lia")
lia: Pet = Pet(lia)

scala> val nobody: Pet = null

<console>:6: error: type mismatch;
 found   : Null(null)
 required: Pet
       val nobody: Pet = null
                         ^

A person who can optionally have a pet, might look like this:

scala> case class Person(name: String, pet: Option[Pet])
defined class Person

scala> val michael = Person("Michael", Some(lia))
michael: Person = Person(Michael,Some(Pet(lia)))

scala> michael.pet get

res0: Pet = Pet(lia)

Unfortunately we don’t get away from NPEs that easy, though. Scala has a tight integration with Java, which is one of its biggest strength. But this tight integration is twofold; Right now, the Scala library itself doesn’t use the NotNull trait.

This leads us quickly back to the initial problem we are trying to avoid:

scala> val jim = Person("Jim", null)
jim: Person = Person(Jim,null)

scala> jim.pet get
java.lang.NullPointerException
...

For now, I ended up with the following definition of Person:

scala> class Person private (val name: String, val pet: Option[Pet]) {
     | def this(name: String) = this(name, None)
     | def this(name: String, pet: Pet) = this(name, Some(pet))
     | }

defined class Person

scala> val me = new Person("Michael", lia)
me: Person = Person@a5ce92

scala> val jim = new Person("Jim")
jim: Person = Person@17820c3

scala> def petName(person: Person) = person.pet match {
     | case Some(pet) => pet.name

     | case None => "NO_PET"
     | }
petName: (Person)String

scala> petName(me)
res1: String = lia

scala> petName(jim)

res2: String = NO_PET

scala> val jim = new Person("Jim", null)
<console>:7: error: type mismatch;
 found   : Null(null)
 required: Pet
       val jim = new Person("Jim", null)

Archive

architecture