?.let vs if not null

What is actually the difference and what is the preferred to use:

variable?.let { //... }

or

if (variable != null) { //... }

6 Likes

Case 2 doesn’t work with mutating properties, instead in case 1 the variable it is always not null.

16 Likes

While I use the first for the reason described by fvasco (you’d have to use varable!! in many cases), I don’t find it to be really clear for those new to the language. To address, I created a function whenNotNull(), which I can use as:

whenNotNull(variable) { }

This seems slightly more self documenting and understable from my perspective.

9 Likes

Could you please share that funciton? There is an simple if (variable != null) statement inside? :smiley:

I just made this:

fun Any?.notNull(f: ()-> Unit){
    if (this != null){
        f()
    }
}

so we can use

var s: String? = null`

s.notNull{
    println("s is not null")
}

What about this approach in comparison to 1) and 2) ?

1 Like

My function is

inline fun <T:Any, R> whenNotNull(input: T?, callback: (T)->R): R? {
    return input?.let(callback)
}

The version with the receiver would also work fine; just a variation on a theme.

7 Likes

Another option is to use an immutable copy

val copy = variable
if (copy != null) {
  // copy is guaranteed to be to non-nullable whatever you do
}
4 Likes

I just found out the difference i was looking for, from this video: 10 Kotlin Tricks in 10 ish minutes by Jake Wharton - YouTube

When i use an if statement with a var, the var can be changed by some other thread in the middle of my if statement, whilst with ?.let {}, the var is read only once and then used inside the let closure, i suppose similarly to the copy method suggested by @Filipp.Riabchun

So as i assume all var variables in kotlin are volatile, the ?.let method is more preferred with them, while with val there is no difference between let and if

4 Likes

Variables in Kotlin are not any more volatile than they are in Java. They certainly don’t get the volatile keyword. Kotlin does however try to be thread-safe by default though as long as you don’t break your thread safety yourself (you need to provide some sort of synchronization (volatile, synchronized, locks)).

3 Likes

It’s not about thread safety, it’s about null-safety. Kotlin tries to provide null-safety even when the variable could be changed from another thread. If you checked your local variable and it’s not null, nothing in the world could make it null, so Kotlin compiler allows to treat is as a non-null value. If you checked your field and it’s not null, it could become null at any moment, so if you use it after check, Kotlin still can’t guarantee that NPE won’t fire, so it doesn’t allow you to use it as a non-null value.

That’s not convenient, if you ask me. In 99.99% cases fields are not shared between threads, so I would take a theoretical risk of getting NPE over a convenience (or just introduce implicit local variable automatically). There are too many cases when I’m copying field to local variable just so null-check would work.

4 Likes

This is not about thread safety as you wrote. The field could be set to null by the current thread during any random method call or whatever.

if(field != null) {
    doSomething() // could set field to null
    field.foo() // nobody can guarantee here that field is != null
}

And automatically copying to a local variable doesn’t make sense here because it changes semantics in case you want to change the field somewhere.

14 Likes

I took the liberty to update your code:

fun <T : Any> T?.notNull(f: (it: T) -> Unit) {
    if (this != null) f(this)
}

This way if you have something like this, it will compile:

    val someString: String? = null
    someString.notNull { doSomething(it) }

    fun doSomething(param: String) {
        // do your stuff here
    }
3 Likes

Do we really need to declare concrete type here T:Any?

We do because we want T to be a non-nullable type (since we define nullability elsewhere in the call). If we allowed for ‘Any?’, there’d be no way to define that the parameter of the callback was non-null.

2 Likes

hiding features of the language in macros/functions is never a good thing. the v?. notation MUST be understand as “a=b++” or “c=*p++” is in C.

2 Likes

you are obviously free to do what you want in your codebase but I would recommend that you stick to

value?.let { it.doIt() }

over your own constructs that do the same. The reason is that your version is only more legible to you until you become more familiar with kotlin. At that point ?.let will be an idiom that you understand simply from scanning over the code while your thing becomes friction that you have to step into to see if it’s even doing what you think it does.

I wouldn’t sweat

 if( value != null)

vs

value?.let{}

too much and just do what’s most natural at that point in the code.

3 Likes

Just a quick note that moving forward, we would want custom helpers like those above to include contracts:

contract {
	(input != null) implies callsInPlace(callback, InvocationKind.EXACTLY_ONCE)
	(input == null) implies callsInPlace(callback, InvocationKind.NEVER)
}

Unfortunately, the current contract DSL doesn’t support any of this; I don’t see any reasonable contract that can be expressed right now besides

contract {
	callsInPlace(callback, InvocationKind.AT_MOST_ONCE)
}
1 Like

Remember that contracts in many ways work in a reverse way. They are logic inference rules dressed up as Kotlin-like code. The problem with the contract you state is that callsInPlace is not detectable. It is an input contract. The null-value is a result contract in that for example if you see a null result you must have had a null input, or if you have a null input you will have a null output.

1 Like

I’m aware. Kotlin contracts currently don’t support what I wanted to express, as I’ve said.

As a consequence, we get strictly less from a helper like whenNotNull then from the unfortunate ?.let idiom, as the compiler won’t know whether the lambda was executed.

just variable is too simple
check this out

getValue()?.let{value->... }

try to write similar in old style to see the difference

The actual difference is whether the receiver object is copied or not.

“?.let” copies and qualifies its receiver as val. So we can treat the copy of receiver as immutable copy. When you only want to use the information of the receiver as nonnull value and don’t have to modify it, “?.let” is suitable.

On the other hand, “if not null” does not capture the receiver and can’t ensure the existence of variable because it can be set to null concurrentlly. So I think “if not null” can often be redundant check.

1 Like