It’s dangerous to conflate foo?.let { bar(it) } ?: baz()
with if (foo != null) bar(foo) else baz()
.
Say you have a function: fun computeElements(): List<Int>? = emptyList()
Consider this code:
val maxElement = computeElements()?.let { it.max() } ?: return
println("Max element was $maxElement")
Compared to:
val list: List<Int>? = computeElements()
val maxElement = if (list != null) list.max() else return
println("Max element was $maxElement")
You may think these are two equivalent forms. However, if you run both, you’ll see that the former does not print anything to stdout!
This is because it.max()
returns null
for an empty list (because there is no max element), which causes the right-hand side of the Elvis expression to be evaluated, and so the function return
s early.
In short, ?.let { ... } ?: ...
allows both branches of the “if-else” to be evaluated, which is dangerous. Aside from this form not being readable (if-else
is universally understood, while let-run
is not), subtle bugs can occur.