I've encountered a situation where Kotlin's nullable type checking is doing the opposite of what I'd expect. Have I misunderstood something fundamental, or is the following not expected behavior?
The return of getOrNull for an Optional of a non-nullable type can be checked for null without the compiler complaining.
class Compiles_TypeNonNullable {
fun foo(o : Optional<String>): String {
val result = o.getOrNull() ?: "fallback"
return result
}
}
While trying to add a null check when the Optional is for a nullable type results in a compile error.
class DoesNotCompile_TypeNullable {
fun foo(o : Optional<String?>): String {
val result = o.getOrNull() ?: "fallback"
return result
}
}
Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
[ERROR] public fun <T : Any> Optional<TypeVariable(T)>.getOrNull(): TypeVariable(T)? defined in kotlin.jvm.optionals
This is the precise opposite of what I'd expect.
The above was observed with both Kotlin 1.9.10 and 1.9.23.
getOrNullonly works onOptional<T>s whereTis non-nullable. This is clear in it's declaration:The
: Anyconstraint disallows any nullable types forT.: Anyisn't technically needed here. The declaration still compiles without it, and it will return the wrapped non-null value or null if the optional is empty. But in Kotlin,Optional<T?>just doesn't make sense. The type constraint not only makes the intended usage clear, it also discourage people from usingOptional<T?>.Here is why
Optional<String?>doesn't make sense.An
Optional<T>either:T, or;This makes sense for
Optional<String>- it either contains a string or is empty.getOrNullreturns the string if it is not empty, and returns null otherwise.According to the same logic, an
Optional<String?>in Kotlin can be any of the following:The thing is,
Optionalcannot represent that second case. Internally, there is just a field of typeT. If this field is null, the optional is empty, otherwise the optional contains an instance ofT.Optionalby design cannot distinguish between the second and third cases.To represent all 3 cases, you would need a
Optional<Optional<String>>or similar.