7

Is this possible if I do a null check before passing? For example:

fun main(args: Array<String>) {
    var num: Int? = null
    // Stuff happens that might make num not null
    ...
    if (num != null) doSomething(num)
}

fun doSomething(number: Int) {
    ...
}

I don't understand why the compiler won't allow me to pass a nullable even though I check that it's not null first. Can anyone explain?

user3307102
  • 305
  • 1
  • 4
  • 15
  • Your specific case is not longer an issue in current Kotlin, the compiler can determine the opportunity of the variable to mutate or not. See answer: http://stackoverflow.com/a/34599753/3679676, or this other question that covers all aspects of nullability and Kotlin's operators: http://stackoverflow.com/a/34498563/3679676 – Jayson Minard Jan 04 '16 at 21:02
  • Also, if you run into one of the situations where the compiler (rightly) can't assume that it's non-null, you can always add the !! to the end of the variable name to tell Kotlin that you trust that it's not null. – Jacob Zimmerman Jan 05 '16 at 11:38

4 Answers4

5

NOTE: starting from compiler version 1.0 beta the code in question works as is

The compiler can tell if the variable is mutated between check and use, at least in case of local variables like in this question, and in some other cases. See Jayson's answer for details.


http://kotlinlang.org/docs/reference/null-safety.html#checking-for-null-keyword--in-conditions says

The compiler tracks the information about the [null] check ... this only works where b is immutable (i.e. a local val or a member val which has a backing field and is not overridable), because otherwise it might happen that b changes to null after the check.

So something like this should work:

fun main(args: Array<String>) {
    var num: Int? = null
    // Stuff happens that might make num not null
    ...
    val numVal: Int? = num
    if (numVal != null) doSomething(numVal)
}

fun doSomething(number: Int) {
    ...
}

Of course, it would be nicer to rewrite "stuff happens" in such a way that you could make num into a val in the first place.

Community
  • 1
  • 1
npostavs
  • 4,877
  • 1
  • 24
  • 43
  • The last part is the important one. If your "stuff" consists of a conditional logic you can write val num = if (condition) stuff else null – Kirill Rakhman Apr 23 '15 at 21:00
  • This can be simplified, first it is no longer an issue in current Kotlin since local variables mutation can be tracked by the compiler. Please add a note to the top of this answer to that affect. It is also now more complicated which cases work and do not; with more being added as the compiler matures. – Jayson Minard Jan 04 '16 at 21:03
2

In current Kotlin (1.0 beta or newer) you do not have this issue anymore. Your code would compile. A local variable that is val or var can safely Smart Cast since the compiler can determine if the value could have mutated or not (on another thread for example).

Here is an excerpt from another Stack Overflow question that covers more aspects of nullability and Kotlin's operators for dealing with them.

More about null Checking and Smart Casts

If you protect access to a nullable type with a null check, the compiler will smart cast the value within the body of the statement to be non nullable. There are some complicated flows where this cannot happen, but for common cases works fine.

val possibleXyz: Xyz? = ...
if (possibleXyz != null) {
   // allowed to reference members:
   possiblyXyz.foo()
   // or also assign as non-nullable type:
   val surelyXyz: Xyz = possibleXyz
}

Or if you do a is check for a non nullable type:

if (possibleXyz is Xyz) {
   // allowed to reference members:
   possiblyXyz.foo()
}

And the same for 'when' expressions that also safe cast:

when (possibleXyz) {
    null -> doSomething()
    else -> possibleXyz.foo()
}

// or

when (possibleXyz) {
    is Xyz -> possibleXyz.foo()
    is Alpha -> possibleXyz.dominate()
    is Fish -> possibleXyz.swim() 
}

Some things do not allow the null check to smart cast for the later use of the variable. The example above uses a local variable that in no way could have mutated in the flow of the application, whether val or var this variable had no opportunity to mutate into a null. But, in other cases where the compiler cannot guarantee the flow analysis, this would be an error:

var nullableInt: Int? = ...

public fun foo() {
    if (nullableInt != null) {
        // Error: "Smart cast to 'kotlin.Int' is impossible, because 'nullableInt' is a mutable property that could have been changed by this time"
        val nonNullableInt: Int = nullableInt
    }
}

The lifecycle of the variable nullableInt is not completely visible and may be assigned from other threads, the null check cannot be smart cast into a non nullable value. See the "Safe Calls" topic below for a workaround.

Another case that cannot be trusted by a smart cast to not mutate is a val property on an object that has a custom getter. In this case the compiler has no visibility into what mutates the value and therefore you will get an error message:

class MyThing {
    val possibleXyz: Xyz? 
        get() { ... }
}

// now when referencing this class...

val thing = MyThing()
if (thing.possibleXyz != null) {
   // error: "Kotlin: Smart cast to 'kotlin.Int' is impossible, because 'p.x' is a property that has open or custom getter"
   thing.possiblyXyz.foo()
}

read more: Checking for null in conditions

Community
  • 1
  • 1
Jayson Minard
  • 84,842
  • 38
  • 184
  • 227
  • days ago I write this code, the ide asked me to add null check: if (a=null) {a = initA()} invokeA(a) // here ide thinks a is nullable, although initA() returns a non-null value – user1453345 Jan 05 '16 at 04:42
  • Comments are not a good place to ask new variations of questions @user1453345. There is missing detail in your question, and a comment doesn't allow for what is required. The information to solve your question is above. You likely have some variation of the 2nd to last example (obviously `a` could mutate or flow analysis can't determine if possible). If that isn't clear, then you are wanting more interactive support in understanding why, try the [Kotlin community on Slack](https://kotlinlang.org/community.html) – Jayson Minard Jan 05 '16 at 07:53
1

You can use let to simplify the code. The kotlin scope function introduces a local variable in the context of "num". No need to declare temporary variable numVal.

fun main(args: Array<String>) {
    var num: Int? = null
    // Stuff happens that might make num not null
    ...
    num?.let{
        doSomething(it)
    }
}

Which works same as below but simpler and cleaner.

fun main(args: Array<String>) {
    var num: Int? = null
    // Stuff happens that might make num not null
    ...
    val numVal: Int? = num
    if (numVal != null) doSomething(numVal)
}
Ray Lee
  • 51
  • 5
1

Use can use Scoping function let or apply along with null safe operator ?.

 fragmentManager?.let{
        viewPager.adapter = TasksPagerAdapter(it)
    }

This way you can pass a nullable type to a non-nullable type parameter

Alok Gupta
  • 1,806
  • 22
  • 21