8
votes

Is Kotlin ?.let thread-safe?

Let's say a variable can be changed in different thread. Is using a?.let { /* */ } thread-safe? If it's equal to if (a != null) { block() } can it happen that in if it's not null and in block it's already null?

1
I guess that it would be too much to make this operator thread safe - Valeriy Katkov
a can be null when the block is executed, but it can not. I.e. it's equivalent to val copy = a; if (copy != null) { block(copy) } - JB Nizet
@4ntoine When the Kotlin compiler smart casts a nullable type to a non-nullable type, you can be sure that it is really non-null. If the code was not thread safe, the compiler would have given you a compiler error (like it does if you do if (a != null) { a.someFunction() }) - marstran
The compiler error it would have given you is this (if a was of type Int?): Smart cast to 'Int' is impossible, because 'a' is a mutable property that could have been changed by this time - marstran

1 Answers

8
votes

a?.let { block() } is indeed equivalent to if (a != null) block().

This also means that if a is a mutable variable, then:

  1. a might be reassigned after the null check and hold a null value when block() is executed;

  2. All concurrency-related effects are in power, and proper synchronization is required if a is shared between threads to avoid a race condition;

However, as let { ... } actually passes its receiver as the single argument to the function it takes, it can be used to capture the value of a and use it inside the lambda instead of accessing the property again in the block(). For example:

a?.let { notNullA -> block(notNullA) }

// with implicit parameter `it`, this is equivalent to:
a?.let { block(it) }

Here, the value of a passed as the argument into the lambda is guaranteed to be the same value that was checked for null. However, observing a again in the block() might return a null or a different value, and observing the mutable state of the given instance should also be properly synchronized.