The Differences Between 'if' and 'let' in Kotlin

5 min read
May 27, 2024

Is it better to use “if” or “let” when dealing with nullable type objects in Kotlin?

Let’s start with the problem:


class IfOrLet {

private var string1: String? = "some string 1"

init {
if(string1 != null) {
makeString1Null()
println(string1) // prints null
}
}

private fun makeString1Null() {
string1 = null
}

}

Using If

In the simple example mentioned, when we use “if”, we are basically verifying a condition. For instance, we might want to execute a specific action only if a certain variable isn't null. This approach is useful for basic conditional checks. But?

Issue? What if while the code within "if" is running, another thread turns "string1" to null?

Issue? Consider what happens if, while the code within “if” is running, another thread sets “string1” to null.

If we perform any operation on "string1" assuming it is not null, our code might not work correctly.

Solution? → Using “let

Solution? → Using “let

class IfOrLet {

private var string1: String? = "some string 1"

init {
string1?.let { it ->
makeString1Null() // <----- even if this make string1 null, the actual value before entering the let block (it) will not change
println(it) // prints "some string 1"
}
}

private fun makeString1Null() {
string1 = null
}

}

Using let

In the provided code, it's clear that if “string1” turns out to be null, it won't impact the operations within the “let” block. But why is that?

First, let's take a look at the code for the “let” function.

public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}

let” is an inline extension function in Kotlin, particularly useful for handling nullable types. By using “let”, we indicate that “if this variable is not null, perform a certain action with it.” In the presented code snippet, we utilized “string1?.let { … }”, where “{ … }” signifies the operation to be executed on “string1” if it’s not null.

Decompiled

To grasp why “let” is thread-safe, let’s examine the decompiled version of the code mentioned above.

public final class IfOrLet {
private String string1 = "some string 1";

private final void makeString1Null() {
this.string1 = null;
}

public IfOrLet() {
// Decompiled form of "let"
String var10000 = this.string1;
String var1;
if (var10000 != null) {
var1 = var10000;
this.makeString1Null();
System.out.println(var1);
}

// Decompiled form of "if"
if (this.string1 != null) {
this.makeString1Null();
var1 = this.string1;
System.out.println(var1);
}

}
}

Ignoring the names, the key point here is that using “letcopies the original value (var10000) for further calculations, disregarding the actual value (string1).

A significant distinction between “if” and “let” lies in their treatment of the variable they manipulate. In the given example, even if string1 turns null within the “let” block, the pre-block value of string1 is already duplicated. This characteristic makes “let” a more secure choice in scenarios where the variable’s value could change unexpectedly, particularly in multithreaded environments.

Other Use Cases

A frequently encountered scenario for using "let" involves performing sequential operations on variables that may be null. For instance, consider having a variable for user input that could be null, and you need to execute a series of operations on it only when it is non-null. By chaining multiple "let" calls, you can manage this process smoothly.

userInput?.let { validateInput(it) }?.let { processInput(it) }?.let { displayResult(it) }

In this example, every “let” block runs only if the one before it produces a non-null outcome, enabling the secure and efficient handling of nullable values.

Read more in Tech