Null safety
A value that may be missing, the question mark that says so in the type, the safe call and the fallback, and the double bang you should reach for last.
Some answers really are missing
A lookup in a map may find nothing. A search for a name may end with no match. In Kotlin that possibility is written into the type: String is text, and String? is text that may be missing. The question mark is the whole of the syntax and most of the safety. It is part of the type, so it travels with the value through every function that accepts it.
The compiler will not let you treat a String? as a String. Calling length on a value that might be null is refused at build time, which turns the oldest crash in programming into a message on the line that caused it. The refusal is not a suggestion: there is no implicit conversion, so the only way forward is to handle the missing case.
Ask safely, or give a fallback
A safe call adds a question mark before the dot: known?.length gives the length when there is a value and null when there is not, without stopping the program. Chain the calls and the whole expression gives up quietly at the first null, which is often exactly the behaviour you want. The type of the result is nullable too, so the question mark follows the answer as well.
The Elvis operator — a question mark and a colon — supplies the other answer: unknown ?: "no role" means use the value if it is there, otherwise the text after the colon. It is the same question a branch would ask, asked once. The right side is evaluated only when the left is null, which makes it safe to put an expensive call or a throwing expression there.
The double bang, and when not to use it
Two exclamation marks assert that a value is not null: known!!.length gives the length, and it throws if it turns out to be null. It is the escape hatch out of the type system, and the compiler accepts it without further argument. The type of the result is non-null, which is why the rest of the expression can be written as if the question mark were never there.
Treat it as a confession: you are telling the compiler you know better. In a program that reads its own data, that is usually true — but every double bang is a place where today is the day the program stops, so the safe call or the fallback is almost always the better line.
Smart casts and the check that stays
After a check such as if (value != null), the compiler smart-casts the value to the non-null type inside the branch. That is the quiet way to use a nullable value: check once, then call its members without a question mark. The cast is only valid while the compiler can prove nothing changed the value between the check and the use.
A val is easy to prove; a var is not, because another function could change it in between. That is why the compiler sometimes refuses a smart cast that looks obvious. Copy the value into a val first, or use ?.let { ... }, when you need the non-null value across more than one line.
The four ways to handle a maybe
fun roleOf(name: String): String? {
return if (name == "Ada") "mathematician" else null
}
fun main() {
val known = roleOf("Ada")
val unknown = roleOf("Alan")
println(known?.length) // 13
println(unknown?.length) // null
println(unknown ?: "no role") // no role
println(known!!.length) // 13, and a crash waiting
// The fallback can be stored like any value.
val role = unknown ?: "unknown"
println("Role: " + role)
}A role that may not exist
Run it and watch what each of the four forms does when there is no answer. Then change the function so that Alan has a role too, and predict which printed lines change before you run it again.
Nullability is checked before the call
String? and String are different types, and the compiler keeps them apart on purpose. A safe call value?.length is an instruction with a branch built in: when the value is there, call the member; when it is null, the whole expression is null and nothing is called. Chain two safe calls and the first null stops the chain, so the answer is null rather than an error.
The Elvis operator ?: is the other half. It evaluates the left side once, and evaluates the right side only when the left is null; that is why a fallback can be expensive or even throw without costing anything on the normal path. After a check such as if (value != null), the compiler smart-casts the value to the non-null type inside the branch, which is the quiet way to use it.
Three null mistakes are worth avoiding. !! turns a maybe into a promise, and a wrong promise is a runtime crash on that line — reach for ?. or ?: first. A safe call still returns a nullable value, so value?.length + 1 does not compile; supply a fallback before doing arithmetic. And a var of nullable type can be changed between a check and its use, so the compiler may refuse a smart cast that would be safe for a val.
Try these next
- Write a function that returns
String?, then print its result with a safe call and with an Elvis fallback. - Chain two safe calls on a value that may be null at either step, and print the result.
- Replace one
!!in a program of your own with a fallback and explain what the fallback should be.
A profile with a city that may be missing
Run it and find every line that handles the missing city. Then give Alan a city, remove Ada city, and predict which printed lines change before you run it again.
A map lookup and a nullable type are the same idea seen from two sides: both say the answer may be missing. When you see the question mark, slow down for one line — the missing case is the line a reader will want to have thought about.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does the question mark in String? mean?
