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 above simple example, when we use “if”, we’re essentially checking a condition. For example, we might want to perform an action only if a certain variable is not null. This is useful for simple conditional checks. But?
Problem? What if during the execution of code inside “if”, some other thread makes “string1” null?
If we do any operation on “string1” considering it non null, our code may behave in wrong manner.
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 above code, we can see even if the “string1" becomes null it will not affect the code inside “let” block. But how?
Let’s first check the code of “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 which is better for dealing with nullable types. When we use “let” we’re saying, “if this variable is not null, do something with it.” In the above code, we used “string1?.let { … }”, where “{ … }” is the action you want to perform with “string1” if it’s not null.
Decompiled
Let’s check the decompiled version of above code to understand how “let” is thread safe:
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 main thing to notice above is that when we use “let”, it copies the original value (var10000) and use it for further calculation and doesn’t care about the actual value (string1).
One key difference between “if” and “let” is how they handle the variable they’re operating on. In above example, even if string1
becomes null inside the “let” block, the original value of string1
before entering the block is already copied. This makes “let” safer to use in situations where the value of the variable might change unexpectedly, such as in multithreaded environments.
Other Use Cases
One common use case for “let” is chaining operations on nullable variables. For example, you might have a nullable variable representing a user input, and you want to perform several operations on it only if it’s not null. You can chain multiple “let” calls together to handle this gracefully.
userInput?.let { validateInput(it) }?.let { processInput(it) }?.let { displayResult(it) }
In this example, each “let” block only executes if the previous one returns a non-null result, allowing for safe and concise handling of nullable values.
Source code: Github
Contact Me:
Happy Coding ✌️
Top comments (1)
++1 let is threadsafe