Exit Full View

Kotlin's let() function

I was looking at kotlin Q&As and tutorials about the let() function, and they all miss the point.

The most common use of let() in the wild, is to "get around" null pointer issues, such as :

class Foo {
    var str : String? = null

    fun bar() {
        str?.let { println( str.size() ) }
    }
}

But first, let's look at why we need let() when str is not nullable :

class Foo {
    var str : String = "Hello"

    fun firstAndLast() {
        println( str.firstOrNull() )
        println( str.lastOrNull() )
    }
}

The code above is WRONG! Try to work out why, before reading on.

str cannot be null, and we are even guarding for str being an empty string. What edge case are we missing?

str is mutable, and therefore it is possible that str changes between the calls to firstOrNull() and lastOrNull(). Therefore firstAnLast() could return a mishmash of two different strings. That's a tricky bug to spot!

The "proper" thing to do, is to make a LOCAL reference to str, so that even if str changes, we will only ever work on ONE of its values. We *could* do this using the let() function :

class Foo {
    var str : String = "Hello"

    fun firstAndLast() {
        str.let {
            println( it.firstOrNull() )
            println( it.lastOrNull() )
        }
    }
}

But why bother using let, we could just as easily, (and I would argue, much clearer), if we use a local variable :

class Foo {
    var str : String = "Hello"

    fun firstAndLast() {
        val local = str
        println( local.firstOrNull() )
        println( local.lastOrNull() )
    }
}

Same number of lines code (less if you count closing braces), less indentation, simpler. What's not to like?

As a bonus, we can even name the local variable 'str', which has the advantage that it is unlikely that we reference the mutable version of 'str' by mistake (though not impossible: this.str)

class Foo {
    var str : String = "Hello"

    fun firstAndLast() {
        val str = str // Yes this is valid, and does do something very useful!
        println( str.firstOrNull() )
        println( str.lastOrNull() )
    }
}

Okay, so far, I've argued that let() is pointless for non-nullable values. How about the common use case with nullable values?

class Foo {
    var str : String = "Hello"

    fun greeting() {
        str?.let { println( str ) }
    }
}

Note, in this case, using ?.let() is one less line of code (the assignment and the null test are merged into 1 line).

I have no problem with the code above, but very soon, somebody will read ?.let() as if non null and want an else block too.

I've seen people try to be clever, and do this :

class Foo {
    var str : String = "Hello"

    fun greeting() {
        str?.let {
            println( str )
        } ?: run {
            println( "It's null!" )
        }
    }
}

I have two issues with this. The first is obvious, we are trying to construct an if..else.. structure, but using the words let and run. Fail!

But more worrying, this pattern doesn't work! The return value of let() is the last statement in the block. So both paths can be executed. e.g. :

class Foo {
    var str : String = "Not null"

    fun greeting() {
        str?.let {
            println( it )
            callSomethingWhichReturnsNull()
        } ?: run {
            println( "It's null!" )
        }
    }
}

This prints

Not null
It's null!

Oops!

The correct solution here is simply :

class {
    var str : String = "Hello"

    fun greeting() {
        val str = str
        if ( str != null ) {
            println( str )
            callSomethingWithReturnsNull()
        } else {
            println( "It's null!" )
        }
    }
}

One more line of code, but I think you'll agree, that being bug free is worth the extra line ;-)

Again, let() isn't useful.

Summary

So, when should you use let()? I'm not sure (yet).

I've been using ?.let() for years without giving it a second thought. I'm fairly sure, now that I've thought about it, many will be weeded out and replaced.