user3307102
user3307102

Reputation: 305

How to pass nullable type into function that takes a non null type?

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?

Upvotes: 7

Views: 13497

Answers (4)

Alok Gupta
Alok Gupta

Reputation: 1910

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

Upvotes: 1

Ray Lee
Ray Lee

Reputation: 51

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)
}

Upvotes: 1

npostavs
npostavs

Reputation: 5037

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.

Upvotes: 5

Jayson Minard
Jayson Minard

Reputation: 85996

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

Upvotes: 2

Related Questions