Reputation: 7788
Today I encountered strange behavior while creating some Kotlin generics. Code below illustrates the problem.
MCVE
class Generic<T>(private val initial: T? = null, private val source: (T)->Unit) {
fun test(value: T) = source(value)
}
fun <T> createGeneric(initial: T? = null, source: (T)->Unit) = Generic(initial, source)
fun test() {
val generic = createGeneric(null) { arg: Int -> println(arg) }
generic.test(42)
}
The line generic.test(42)
produces error:
The integral literal does not conform to the expected type Nothing
Question: How can T
be deduced as Nothing
, since I pass { arg: Int -> println(arg) }
lambda, which is (Int)->Unit
?. I double-checked type of variable generic
, and it's deduced as Generic<Nothing>
.
Also, if the type is deduced as Nothing
, why it allows to pass lambda of type (Int)->Unit
as second argument?
Upvotes: 3
Views: 195
Reputation: 3339
This is because your lambda can never execute, because the function invocation source(value)
can never happen.
In Kotlin, whenever you have a function that doesn't finish, the function returns a type Nothing
.
If you were to define the type that your Generic takes in Generic<Int>
then you would get rid of that error. But because you don't, the compiler tries to infer it based on the value that you're passing in createGeneric(null)
. Because you're passing in null
.
When you pass in null on a parameter type, it resolves to Nothing?
and when you pass it into a nullable parameter type it resolves to Nothing
.
Nothing
in Kotlin, is the absence of type. Because null
itself doesn't have a type, you get Nothing
.
Since your T
is now Nothing
in the context of your entire Generic
class, your test function signature is now test(value: Nothing)
. which is why you get your error when you try to pass in an Int into that test
function.
As to your second point "Why can I pass in (Int) -> Unit"?
val generic = createGeneric { arg: Int -> println(arg) }
If you were to get rid of the null
parameter, you can see that the next error that you get will be because you're passing in (Int) -> Unit
and it can't correctly infer that parameter type to be true.
Upvotes: 0
Reputation: 170713
Because functions are contravariant in their argument, (Int) -> Unit
is a subtype of (Nothing) -> Unit
. So both arguments are acceptable for T=Nothing
and that's what gets inferred.
Yes, they are also both acceptable for T=Int
, but Kotlin chooses the more specific type.
Upvotes: 2