alekop
alekop

Reputation: 3026

How does the let function guarantee the value won't be null?

The let function guarantees the value inside the block won't be null, even if another thread changes the original value by the time the block is run... Sounds good, but how does it do that?

var user: User?

user?.let { it ->
  // `user` can be null at this point, but not `it`.
}

How does the compiler prevent the underlying object from being deallocated while this block is running? Strong reference? Critical section? Something else?

Upvotes: 2

Views: 930

Answers (3)

Raman
Raman

Reputation: 19595

Null-Safe Operator ?.

This behavior actually has little to do with the let function and has more to do with the ?., also known as a null-safe operator.

If you have a nullable reference user as in your example, a statement such as the following which includes the null-safe operator ?.:

val n = user?.name

says: if user is not null, use it for the next part of the call chain (in this case referencing the name property) and assign the result of that to the val n, otherwise assign null to n.

Scoping Functions

let is simply one of the Kotlin scoping functions -- its definition in code is simple. let takes its "receiver", or the value it is being called on, and passes that value as the first parameter to the function that is passed to let i.e. the { it -> ... } lambda. That lambda parameter name is by default it, so the it -> part is actually redundant.

When the lambda block ends, that variable goes out of scope, hence it is "scoped" to that specific block i.e. it simply assures that the variable does not exist beyond that block.

Bringing it Together

Now, combining the idea of the null-safe operator with the let scoping function, we have the code you wrote:

user?.let {
  // `user` can be null at this point, but not `it`.
}

The null-safe operator ?. guarantees that if user is not null, then the next part of the chain is executed, which in this case is the let scoping function. If user is not null, then the not-null user becomes the receiver of the let and its value is assigned to it.

The receiver of the let function is a copy of the non-null reference, so it is not affected by changes in the reference of the original user var e.g. if user is set to null by another thread.

If the user is already null, then the let function is never even called. In other words, the let block is never executed because of the null-safe operator.

This code is equivalent to a copy of the user variable into a local variable, then an if statement checking if that copy is not null, which Kotlin handles with a smart cast for this situation:

val u = user
if (u != null) {
  // u cannot be null inside this block, due to smart cast
}

However, ?.let { ... } is a bit more succint by avoiding the explicit assignment, and can read better when its return value is used to continue the call chain.

One last thing... if you refer back to the definition of let, you can see it allows a nullable receiver. That means it is valid to call a let function on a possibly null value, and when doing so, the it is nullable too:

user.let {
  // it can be null here
}

Upvotes: 3

gidds
gidds

Reputation: 18577

The short answer is that let makes no such guarantee — in fact, like many extension functions, it can be called on a null value.

What prevents a null in this case is the ?. safe-call operator. That calls the following function only if the previous value is non-null. So if user is null, it won't even call let. That's why the compiler knows that if it gets into the let lambda, the receiver must have been non-null.

And yes, while the lambda is being executed, the it reference makes the user object reachable, and so not eligible for garbage collection.

Upvotes: 1

Tenfour04
Tenfour04

Reputation: 93649

It is equivalent to copying the value to a local variable and then using that. Your code is equivalent to:

val it = user
if (it != null) {
  // `user` can be null at this point, but not `it`.
}

except that it is confined to the scope of the lambda. So, yes, it creates a strong reference.

By the way, it only guarantees it is not null if using the null-safe call ?.. If you use a standard .let call on a nullable, it very well could be null inside the lambda.

Upvotes: 5

Related Questions