Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Questions about kotlin's extension function and null-related casting (kotlin in action)

Tags:

kotlin

I'm studying while reading Kotlin in action.

While reading a section called Type Extensions, whis is 6.1.9 "Extensions for nullable types", I wrote the code like this.

fun verifyUserInput(input: String?) {
    if (input.isNullOrBlank()) { 
        println("Please fill in the required fields")
        return
    }
    println("input length: ${input.length}") // do not need null-check
}

At the last line's input in the code above, you can access the length property without safe-call(?) or not-null assistance(!!).

But Here's another code sample.

fun verifyUserInputNormal(input: String?) {
    if (nullOrBlank(input)) {
        println("Please fill in the required fields")
        return
    }
    println("input length: ${input.length}") // compile Error!
}

fun nullOrBlank(input: String?): Boolean {
    return input == null || input.isBlank()
}

This time, a compilation error occurs in the last line of the method verifyUserInputNormal. The compilation error's like Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?

Does anyone know why this difference is happening?

The book doesn't expalin clearly about this.

Thank you

I asked Chat Gpt this. But it keeps talking nonsense..... :D

like image 279
sh1mj1 Avatar asked Oct 23 '25 07:10

sh1mj1


1 Answers

The Kotlin compiler uses flow typing, which allows a variable to have a more specific type once you performed some checks. This is why after checking for null explicitly, the rest of the code knows that the variable isn't null, and thus you don't need safe-calls with ?..

That said, these type refinements (called smart casts) are pretty local: they only work when the null check is right here and not buried in a function call. This is most likely for performance reasons, as inspecting all function bodies for type resolution would have a big impact on compilation time.

But not all hope is lost. To fix this, Kotlin has contracts. These allow developers to give the compiler more explicit information about the deductions it can derive from some function call. A lot of stdlib functions are defined with contracts to allow the kind of smooth experience you're describing in your first example.

Specifically for the stdlib functions isNullOrEmpty or isNullOrBlank, such a contract has been declared: https://github.com/JetBrains/kotlin/blob/6884266328871be8d8ab493c1a4b1f24d30c02a4/libraries/stdlib/src/kotlin/text/Strings.kt#L325-L327

public inline fun CharSequence?.isNullOrBlank(): Boolean {
    contract {
        returns(false) implies (this@isNullOrBlank != null)
    }
    return this == null || this.isBlank()
}

This tells the compiler that if the function returns false, the receiver can be considered non-null for the subsequent code.

In your own function declaration, you didn't specify such contract, and thus the compiler doesn't assume anything from such function call.

like image 138
Joffrey Avatar answered Oct 25 '25 22:10

Joffrey