One thing that I've always kinda brushed over and ...
# getting-started
c
One thing that I've always kinda brushed over and never understood happened again today. So I figure I would ask. Sometimes I do something like
Copy code
if (foo.barr != null){
name = foo.bar!!
}
Why do I need the double bang operator if I just checked if the value was != null?
j
because it can be mutated before you access to it. Assign it to a
val
or use
let
e
imagine a
Copy code
class Foo {
    var bar: Any?
}
that is updated in between your check and your assignment, or a custom getter
Copy code
class Foo {
    val bar: Any?
        get() = field.also { field = null }
that changes nullability after read. if it's in a different compilation unit, Kotlin must assume the implementation may change
c
That's also why using a temporary variable fixes it:
Copy code
val bar = foo.bar
if (bar != null)
    name = bar // no problem
because you're storing it in a read-only local variable, Kotlin now knows that it's not possible for it to become nullable between the condition and the assignment, so it's safe.
p
You should not use
!!
at all. Whenever you see it in code, a better approach is available 100% guaranteed
👎 1
👍 2
j
if you want to avoid the temporary variable, you can use the following idiom:
Copy code
foo.bar?.let { name = it }
p
Some alternatives. If you want non expression if/else behavior, you could do:
Copy code
foo.bar?.also { dosomething(it) } ?: run {
  println("It was null")
}
Expression if/else
Copy code
val bar2 = foo.bar?.let { dosomethingAndReturnAValue(it) } ?: "DefaultValue"
Expression if/else with action
Copy code
val bar2 = foo.bar?.let { dosomethingAndReturnAValue(it) } ?: run {
  println("It was null")
  "DefaultValue"
}
If you have default values:
Copy code
val bar = foo.bar ?: "default"
Early return
Copy code
val bar = foo.bar ?: return
Early return with result
Copy code
val bar = foo.bar ?: return Result.Error("should not be null")
Early return with result and action:
Copy code
val bar = foo.bar ?: run {
  dosomethingWhenNull()
  reportToNonFatalErrorFirebase()
  return Result.Error("should not be null")
}
e
we don't have https://youtrack.jetbrains.com/issue/KT-25698 so we can't express it as nicely as Rust or Swift, but we do have https://github.com/Kotlin/KEEP/blob/master/proposals/val-in-when-subject.md so
Copy code
when (val bar = foo.bar) {
    null -> {
        // ...
    }
    else -> {
        // bar is smart-cast to non-null
    }
}
is also possible
👍 1
p
Yeah swift did good at that
c
nice. personally i hate
let
. I feel like it just makes my head hurt. but maybe thats a good candidate here.
might be a noob question but "because it can be mutated before you access to it." does that basically just mean in a multi threaded environment it could be mutated. but if i know im not doing anything multithreaded, then I would know for sure it wasn't being mutated, right?
yes black 1
🚫 1
e
a. even if you know there's no racing threads, the compiler doesn't know b. my second example demonstrates a
val
changing without any threading involved
1
if it's a
val
without a custom getter in the same compilation unit, then Kotlin should allow the smart cast. otherwise it can't
👍 2
c
a) yep. understood. just wanted to make sure for my knowledge that that's the "main"/typical use case the compiler is saving me from. i can def see using let more often now that I know this.
e
it's not the only use case the compiler is saving you from
suppose it's in a dependency. even if it's a
val
in the version of the dependency you're compiling with, it's possible that your compiled code will get assembled together with a newer version of the dependency in the future in which it is changed to a
var
, a binary compatible change
👀 1
c
thank you all for teaching. leveled up from this question. ❤️ ❤️ ❤️
🚀 1