I have a map of type `Map<Int, String?>` , I...
# getting-started
m
I have a map of type
Map<Int, String?>
, I have a check in place that verifies all map values are not null,
map.all { it.value != null }
and only if this check is true I pass this map down into a function. The problem is my function accepts only non nullable maps of type Map<Int, String>, I don't like unchecked casts or !! bangs because I know for a fact this code supposed to work without them. I decided to fix this using Kotlin Contracts but it seems like it's not possible to make a contract like
Copy code
contract {
            returns(true) implies(values is Map<Int, String>)
        }
due to type erasure. Is there anything I have overlooked? What's the idiomatic way to solve this?
y
Instead of returning a boolean, you could have your function return a
Map<Int, String>?
, note where the ? is
e
it requires an unsafe cast unless you create a new map
in general, there's no guarantee that the underlying map won't change to include a null later on…
Copy code
val map = mapOf<Any, Any?>("A" to "Z")

require(map.values.all { it != null })
val nonNullMap = map as Map<Any, Any> // unsafe cast, safe until…

map["0"] = null
// now nonNullMap contains a null value
m
Obviously I am not aiming to sabotaging my own code
e
yes but it's not safe to do in totally generic code
m
There should be a way to avoid writing unsafe cast gibberish
v
Sure
Copy code
map.mapValues { (_, value) -> value!! }
so beautiful 1
e
or similarly,
Copy code
fun <K, V : Any> Map<K, V?>.nonNullValues(): Map<K, V>? {
    return entries.mapValues { (_, value) -> value ?: return null }
}
either way it's a new map so it can't be made wrong by changes to the original
if you do want to do it the original way, you need the unsafe cast
err, unchecked cast
m
@Vampire Yes, that's the way I currently implemented it. That said I am disappointed Kotlin Contracts are so lackluster
h
This is unrelated to Kotlin contracts. You/ a library can also write your/its own Map implementation and return a nullable value.
😁 1
v
And a satellite could fall down and hit your toe 🙂
y
It is frustrating that this contract isn't allowed though:
Copy code
contract {
  returns(true) implies(values is Map<Int, String>)
}
I understand that in this case, yes, there's dangers involved, but for other use cases, you might have an immutable class that you can't get smartcasts for because of seemingly arbitrary limitations on contracts
m
Kotlin Contracts do not support higher order types. That's the actual problem here This use case is perfect for utilising Contracts, I can guarantee that the map values are of type String, that's literally what contracts are about, about providing guidance to compiler
h
I understand it, but at end, there is no real difference when you are using
!!
or when the compiler passes the type without checking, a nullable value will always crash. It’s absolutely okay to call
!!
when your specific use case allows it.
🤓 1
☝️ 1
e
(or
checkNotNull
,
requireNotNull
, etc. which can provide better messaging but yes)
h
Also, you often want to include some debug/error message, so you should use checkNotNull etc. instead.
Too slow 😂
k
If you do what you're doing now, i.e.
map.mapValues { (_, value) -> value!! }
then the compile will perform a null check for every value. If you feel that's wasteful and unnecessary, you can convince the compiler to believe you when you tell it that the values are non-null, so that it treats them as non-null without actually checking:
Copy code
@Suppress("NOTHING_TO_INLINE")
@OptIn(ExperimentalContracts::class)
inline fun <T> smartCastToNonNull(arg: T?) {
    contract { returns() implies (arg != null) }
}

fun <T> assertNonNull(arg: T?): T {
    smartCastToNonNull(arg)
    return arg
}

... map.mapValues { (_, value) -> assertNonNull(value) }
However, the overhead of
!!
will not be noticeable so I would just use that.
K 1