Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin's 'let' plus elvis, and accidental null return values

Tags:

idioms

kotlin

I was surprised today to learn that this take on apparently idiomatic code fails:

class QuickTest {

    var nullableThing: Int? = 55

    var nullThing: Int? = null

    @Test
    fun `test let behaviour`() {
        nullableThing?.let {
            print("Nullable thing was non-null")
            nullThing?.apply { print("Never happens") }
        } ?: run {
            fail("This shouldn't have run")
        }
    }
}

It happens because, combined with implicit return, nullThing?.apply{...} passes null to the let, and therefore the elvis operator evaluates on null and runs the second block.

This is pretty horrible to detect. Do we have an appropriate alternative beyond conventional if/else without this pitfall?

like image 210
Rob Pridham Avatar asked Nov 21 '18 16:11

Rob Pridham


2 Answers

You could use also instead of let. also will return nullableThing, whereas let will return whatever the lambda returns.

See this article: https://medium.com/@elye.project/mastering-kotlin-standard-functions-run-with-let-also-and-apply-9cd334b0ef84 (point "3. Return this vs. other type").

like image 88
Yoni Gibbs Avatar answered Nov 11 '22 12:11

Yoni Gibbs


Your case is a candidate for also thematic. Compare two block actions:

fun <T> T.also(block: (T) -> Unit): T
fun <T, R> T.let(block: (T) -> R): R

nullableThing?.also {
    print("Nullable thing was non-null")
    nullThing?.apply { println("Never happens") }
} ?: run {
    fail("This shouldn't have run")
}

Another idiomatic way is to use when statement

when (nullableThing) {
    null ->
        print("Nullable thing was non-null")
        nullThing?.apply { println("Never happens") }
    else -> fail("This shouldn't have run")
}
like image 31
Sergei Rybalkin Avatar answered Nov 11 '22 13:11

Sergei Rybalkin