The safe call a?.b evaluates b only when a is not null and yields null otherwise, so a chain collapses at the first gap. The Elvis operator a ?: b (tilt your head to see the quiff) supplies b when a is null, and because return and throw are expressions, its right side can also leave the function:
data class Book(val id: Int, val title: String, val subtitle: String? = null)
val catalog = listOf(Book(1, "The Quiet Harbor"),
Book(2, "Patterns of the Deep Web", "How Services Talk"))
fun find(id: Int): Book? = catalog.find { it.id == id }
fun label(id: Int): String {
val book = find(id) ?: return "No book #$id" // Elvis with an early return
return "${book.title}: ${book.subtitle?.lowercase() ?: "no subtitle"}"
}
fun main() {
listOf(1, 2, 99).forEach { println(label(it)) }
println(find(99)?.subtitle?.length) // the whole chain yields null
}Output
The Quiet Harbor: no subtitle Patterns of the Deep Web: how services talk No book #99 null
After the guard, book has the non-null type Book, so the rest of label() needs no checks. To run a block only when a value exists, write x?.let { ... } (Scope Functions). Unlike ||, ?: reacts only to null, never to 0, "" or false.