Maksym M.
04/17/2024, 4:52 PMMap<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
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?Youssef Shoaib [MOD]
04/17/2024, 5:09 PMMap<Int, String>?
, note where the ? isephemient
04/17/2024, 5:12 PMephemient
04/17/2024, 5:12 PMephemient
04/17/2024, 5:14 PMval 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
Maksym M.
04/17/2024, 5:15 PMephemient
04/17/2024, 5:16 PMMaksym M.
04/17/2024, 5:16 PMVampire
04/17/2024, 5:17 PMVampire
04/17/2024, 5:17 PMmap.mapValues { (_, value) -> value!! }
ephemient
04/17/2024, 5:18 PMfun <K, V : Any> Map<K, V?>.nonNullValues(): Map<K, V>? {
return entries.mapValues { (_, value) -> value ?: return null }
}
ephemient
04/17/2024, 5:18 PMephemient
04/17/2024, 5:19 PMephemient
04/17/2024, 5:19 PMMaksym M.
04/17/2024, 5:19 PMhfhbd
04/17/2024, 5:25 PMVampire
04/17/2024, 5:26 PMYoussef Shoaib [MOD]
04/17/2024, 5:27 PMcontract {
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 contractsMaksym M.
04/17/2024, 5:27 PMhfhbd
04/17/2024, 5:32 PM!!
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.ephemient
04/17/2024, 5:34 PMcheckNotNull
, requireNotNull
, etc. which can provide better messaging but yes)hfhbd
04/17/2024, 5:34 PMhfhbd
04/17/2024, 5:34 PMKlitos Kyriacou
04/17/2024, 9:34 PMmap.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:
@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.