Why does this program prints kotlin.Unit when it should fail with ClassCastException at runtime?
class Animal<T> {
}
fun <T> Animal<T>.extension(block: ()-> T){
print(block())
}
fun main(){
//(4 as Unit) //Runtime ClassCastException, OK
//Animal<String>().extension { 2 2 } //Compilation error, ok
Animal<Unit>().extension { 2 2 } // Why no ClassCastException but prints kotlin.Unit?
}
If this is not a bug, is it possible to enforce the constraint?
CodePudding user response:
Kotlin treats functions that return Unit as a special case when interpreting lambda expressions. If it expects the lambda to return Unit, it will implicitly return Unit regardless of what the last line of the lambda evaluates to. Otherwise, lambdas would very frequently have to have a useless line at the end to return Unit
. So, because of the way it is interpreting the lambda, there is no cast occurring here. There is an implicit extra line returning Unit in your function defined by the lambda.
Suppose you're calling a function that takes a callback parameter fun foo(onComplete: (String)->Unit)
, and you want to add the returned value to a Set:
foo {
someMutableSet.add(it)
}
The add
function returns a Boolean that we don't care about here. It would be annoying if you had to remember to mark it like this:
foo {
someMutableSet.add(it)
Unit
}
This special treatment doesn't just apply to lambdas. You don't have to put a return Unit
line at the end of every traditional function that returns Unit either. And you don't have to explicitly put the return type : Unit
for function signatures either.
CodePudding user response:
Your Int
isn't being cast to a Unit
. It's the other way around, actually. Your () -> Int
lambda is being treated as a () -> Unit
lambda. Specifically, from the docs on lambda expressions (emphasis mine),
The full syntactic form of lambda expressions is as follows:
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x y }
...
- If the inferred return type of the lambda is not
Unit
, the last (or possibly single) expression inside the lambda body is treated as the return value.
So in your example,
Animal<Unit>().extension { 2 2 }
{ 2 2 }
is not a lambda returning an Int
. It's a lambda returning Unit
, i.e. nothing of value. Kotlin is happy to simply discard the return value of a lambda if it makes type inference work out. That's to allow things like
myButton.addEventListener { event ->
...
someFunctionThatHappensToReturnInt()
}
It would be really annoying if that lambda had inferred return type Int
just because I happened to call a function (for side effects) that returns an Int
, so Kotlin will drop it when necessary.
If you write the word return
explicitly, you'll force the type of the argument to be () -> Int
and will see the error you expect.
Animal<Unit>().extension { return 2 2; }