0

Just learned Kotlin Nullable type and let{} function which replaces the if (xx != null) {} operation.

But one thing I am confused is that, we all know and I Think the Complier Should Know that when we use let{}, the variable/object who is calling this function is possiblly null, however the complier still requires me to add the safe call operator "?" after the variable name instead of providing Smart Cast like it does in if (xx != null) {}. Why?

My piece of code:

fun main() {
    var number1: Int? = null

    //val number2 = number1.let { it + 1 } ?: 10    //doesn't work, not quite "smart"
    val number2 = number1?.let { it + 1 } ?: 10     //works, must have "?"

    println(number1)
    println(number2)
}
Sam Chen
  • 7,597
  • 2
  • 40
  • 73
  • 6
    `let` doesn’t do what you think it does. It’s a “scope function” - i.e. it’s a function used for executing a block of code in different scope. It’s has **nothing to do with `null`**. The Kotlin nullsafe operator executes method calls on a reference conditional on that reference being nonnull. So by combining the two `x?.let{ ...}` you execute a block of code in iff the target is nonnull. – Boris the Spider Nov 15 '20 at 16:54
  • Does this answer your question? [What is the purpose of 'let' keyword in Kotlin](https://stackoverflow.com/questions/58606651/what-is-the-purpose-of-let-keyword-in-kotlin) – Boris the Spider Nov 15 '20 at 16:54
  • i agree with @BoristheSpider, it doesn't have anything to do with Null, it's only a scoping mechanism like `run` or `apply` – a_local_nobody Nov 15 '20 at 16:55
  • @a_local_nobody indeed - `let`, `run`, `apply` and `also` are the four Kotlin scope functions. – Boris the Spider Nov 15 '20 at 16:56
  • `let{}` doesn't change anything to the variable/object you're using it on, but if you're using `?let{}` then you're referring to non-null instance of the variable/object, so just using `let{}` you're just using the same variable/object which can still be null – a_local_nobody Nov 15 '20 at 16:59
  • @BoristheSpider add two mores: `use` and `with` :) – Animesh Sahu Nov 15 '20 at 17:51
  • Does this answer your question? [Understanding the need for Kotlin let](https://stackoverflow.com/questions/54382675/understanding-the-need-for-kotlin-let) – Animesh Sahu Nov 15 '20 at 17:54

1 Answers1

4

You've already got answers in the comments, but just to explain the ? thing...

Kotlin lets you make null-safe calls on nullable variables and properties, by adding ? before the call. You can chain this too, by doing

nullableObject?.someProperty?.someFunction()

which evaluates nullableObject, and if it's non-null it evaluates the next bit, otherwise the whole expression evaluates to null. If any part of the chain evaluates as null, the whole expression returns null.

So it has this short-circuiting effect, and you can use the elvis "if null" operator to create a default value if you can't evaluate the whole chain to a non-null result:

nullableObject?.nullableProperty?.someFunction() ?: defaultAction()

and once you introduce the null check in the chain, you have to add it for every call after that - it's basically propagating either the result of the previous bit, or the null it resolved to, so there's a null check at each step


The let block is just a scope function - you use it on a value, so you can run some code either using that value as a parameter or a receiver (a variable or this basically). It also has the side effect of creating a new temporary local variable holding that value, so if the original is a var it doesn't matter if that value changes, because your let code isn't referring to that variable anymore.

So it's useful for doing null checks one time, without worrying the underlying value could become null while you're doing stuff with it:

nullableVar?.let { it.definitelyIsNotNull() }

and the compiler will recognise that and smart cast it to a non-null type. An if (nullableVar != null) check can't guarantee that nullableVar won't be null by the time the next line is executed.

cactustictacs
  • 17,935
  • 2
  • 14
  • 25
  • Ok, so let me make a conclusion. First, the safe call operator `?` is just designed for `Nullable` type. Second, by using safe call `?` on a `Nullable` variable, the following operations (functions/property) called by that variable will be executed ONLY IF IT'S NOT NULL (avoid `NullPointerException`), and this behavior/mechanism is not provided by the `let{}` function. Am I correct? – Sam Chen Nov 16 '20 at 16:48
  • And I think the last part you mentioned is about thread safety, which is a benefit of using `let{}`. I learned that from this article: https://android.jlelse.eu/lets-talk-about-kotlin-s-let-extension-function-5911213cf8b9 – Sam Chen Nov 16 '20 at 16:53
  • Yeah the part about a variable changing could be another thread changing it, or potentially a coroutine on the same thread... basically any time there's potential for a variable to change, and undermine some conclusion you drew earlier (like that it's not null, or that it ``is`` a particular type) you'll get a lint warning about it. If it can safely say that the conclusion won't change, that's when it will usually smart cast for you. So using scope functions gives you a temporary variable (like you'd have to make yourself to be safe) which is guaranteed to stay the same! – cactustictacs Nov 16 '20 at 20:21
  • The ``?`` before a call only makes it if the thing you're calling it on resolves to a non-null value. That prevents you from making calls on things that are null (so don't have the method to call), but you can also use it as a basic null-check - and that's the recommended Kotlin style for "if variable isn't null do this thing with it"-type code, and the elvis operator acts like an ``else``. You can still call ``let`` on a null value - you can do ``null.let { ... }``, it just means the variable passed into the function is ``null``, and it's up to you to handle that safely inside the code block – cactustictacs Nov 16 '20 at 20:29