Kotlin null check for multiple nullable var's

It seems to me that this is what the !! operator is meant for. It’s great that the compiler will often verify that you have checked for nullability, but when it can’t, it seems fine for the developer to enforce it.

How about:

@ExperimentalContracts
fun notNull(a: Any?, b: Any?): Boolean {
    contract {
        returns(true) implies (a is Any && b is Any)
    }
    return (a is Any && b is Any)
}

@ExperimentalContracts
fun foo(a: Int?, b: String?): String {
    if (notNull(a, b)) {
        return b.repeat(a)
    }
    return ""
}

@tango24 would it make sense to make your notNull() inline?

It’s normally better to let the JVM handle inlining. In nearly 100% of all cases you only want to use inline if you have to deal with reified types or take a lambda argument. Otherwise there is no real performance gain and you just increase the size of the class file.
Basically you don’t want to use inline when kotlin shows a warning about it :wink:

Inline or not, I just wanted to demonstrate the possibility of using contracts.
In fact, simple null-checks are sufficient and probably faster:

@ExperimentalContracts
fun notNull(a: Any?, b: Any?): Boolean {
    contract {
        returns(true) implies (a != null && b != null)
    }
    return (a != null && b != null)
}

I solved this by creating some functions that more or less replicates the behavior of with, but takes multiple parameters and only invokes the function of all the parameters is non-null.

fun <R, A, B> withNoNulls(p1: A?, p2: B?, function: (p1: A, p2: B) -> R): R? = p1?.let { p2?.let { function.invoke(p1, p2) } }
fun <R, A, B, C> withNoNulls(p1: A?, p2: B?, p3: C?, function: (p1: A, p2: B, p3: C) -> R): R? = p1?.let { p2?.let { p3?.let { function.invoke(p1, p2, p3) } } }
fun <R, A, B, C, D> withNoNulls(p1: A?, p2: B?, p3: C?, p4: D?, function: (p1: A, p2: B, p3: C, p4: D) -> R): R? = p1?.let { p2?.let { p3?.let { p4?.let { function.invoke(p1, p2, p3, p4) } } } }
fun <R, A, B, C, D, E> withNoNulls(p1: A?, p2: B?, p3: C?, p4: D?, p5: E?, function: (p1: A, p2: B, p3: C, p4: D, p5: E) -> R): R? = p1?.let { p2?.let { p3?.let { p4?.let { p5?.let { function.invoke(p1, p2, p3, p4, p5) } } } } }

Then I use it like this:

withNoNulls("hello", "world", Throwable("error")) { p1, p2, p3 ->
    p3.printStackTrace()
    p1.plus(" ").plus(p2)
}?.let {
    Log.d("TAG", it)
} ?: throw Exception("One or more parameters was null")

The obvious issue with this is that I have to define a function for each case (number of variables) I need, but at least I think the code looks clean when using them.

That’s quickly done though and you shouldn’t need this for more than 3 or 4 parameters anyway (or you’re doing something else wrong). Maybe we need an issue to add this to the standard library.

1 Like

How about using requireNotNull for all the parameters before accessing them ?
Of course this will be a fail-fast approach .
internal data class MediaMetadata(
val contentURL: String,
val contentType: MediaType,
val licenseURL: String?,
val licenseType: DRMScheme
) {

companion object {
  const val CONTENT_URL = "CONTENT_URL"
  const val CONTENT_TYPE = "CONTENT_TYPE"
  const val LICENSE_URL = "LICENSE_URL"
  const val LICENSE_TYPE = "LICENSE_TYPE"
  fun from(
    contentURL: String,
    mediaType: String,
    licenseURL: String?,
    drmScheme: String
  ): MediaMetadata {
    return MediaMetadata(
      contentURL,
      MediaType.valueOf(mediaType),
      licenseURL,
      DRMScheme.valueOf(drmScheme)
    )
  }

  fun from(
    savedStateHandle: SavedStateHandle
  ): MediaMetadata? {
    return if(savedStateHandle.run {
        contains(CONTENT_URL) && contains(CONTENT_TYPE) && contains(LICENSE_TYPE)
      }) {
      val contentURL = savedStateHandle.get<String>(CONTENT_URL)
      val mediaType = savedStateHandle.get<String>(CONTENT_TYPE)
      val licenseURL = savedStateHandle.get<String>(LICENSE_URL)
      val licenseType = savedStateHandle.get<String>(LICENSE_TYPE)
      requireNotNull(contentURL)
      requireNotNull(mediaType)
      requireNotNull(licenseType)
      from(contentURL, mediaType, licenseURL, licenseType)
    }else {
      null
    }
  }
}

}

I also tried to find a generic solution for this. I ended with following code:

fun <T : Any> allNotNull(vararg elements: T?): List<T>? = if(elements.contains(null)) null else elements.filterNotNull()

I can now write something like

val firstName = parameters["firstName"]
val lastName = parameters["lastName"]

 allNotNull(firstName, lastName)?.apply {
     userService.add(UserDTO(firstName = this[0], lastName = this[1]))
 }

The UserDTO needs String parameters, but parameters[“firstName”] is of type String? . I don’t like the syntax with this[0] much, but it is still better as chaining multiple let-calls.

Please note, that your solution creates unnecessary allocations:

fun <T : Any> allNotNull(/*vararg convers input elements into the new array from heap*/vararg elements: T?): List<T>? = if(elements.contains(null)) null else /*new list is creating here*/elements.filterNotNull()

This can be fixes by different ways. For example, we can create a lot of functions for different arguments counts and do parameters check inside. So, the idea is like this:

inline fun <TFirst : Any, TSecond: Any, TResult: Any> allNotNull(first: TFirst?, second: TSecond?, action: (TFirst, TSecond -> TResult)): TResult? {
    return when {
        first !== null && second !== null -> action(first, second)
        else -> null
    }
}

This is one of the typical logging mistake, when all conditions below are true:

  • Code seems like as allocation-free (requirement validation, logging method execution when it is disabled)
  • However code does allocations inside

Such issues can lead to a lot of new bytes in heap, however source is spread across whole application.

However my code is not ideal too, because if TFirst could not be null then we will do unnecessary null checks. We can just cross fingers on next compiler/JIT optimizations.

I’m aware of the unnecessary allocations, but your solution is not an alternative for me.

“Create a lot of functions for different argument counts” → This is exactly, what I wan’t to avoid.

In my special code situation (offtopic) I can also use

val firstName = parameters.getOrFail("firstName")
val lastName = parameters.getOrFail("lastName")

which is best for exactly this situation, because smart casting works here. But I still prefer my solution, if those functions are not available.

1 Like

Since that doesn’t create local variables it’s not null/thread safe.

You can make a Util function that take array of values and check if they all not null
also added !! operator, but the operator only will leads for a null crashes if null appears.

    fun test(): Boolean {
        if(isNotNull(var1, var2, var3, var4)) {
            return procceed(var1!!, var2!!, var3!!, var3!!)
        }
        return false;
    }

    private fun isNotNull(vararg objArr: Any?) : Boolean {
        for(obj in objArr) {
            if(obj == null)
                return false
        }
        return  true;
    }

Again this function requires new array allocation on stack, because of this parameter - vararg objArr: Any?. The same was answered here.