Carson Graham
Carson Graham

Reputation: 539

Why does kotlin not compile when I have a return inside a lambda inside a loop

So I have this very simple code:

val a:() -> String = a@ {
    while(true){
            return@a "hello"
    }
}

And intellij says:

Type mismatch.
Required: String
Found: Unit

Now, if I had the same thing, like, in a function

fun b():String {
    while(true){
        return "hello"
    }
}

It's totally fine.

I also can't use a Callable

val c : Callable<String> = Callable<String> {
    while(true){
        return@Callable "hello"
    }
}

Same error. I could convert it to an object decleration:

val d :Callable<String> = object :Callable<String>{
    override fun call(): String {
        while(true)
            return "hello"
    }
}

and that works. Why doesn't it work when using a lambda?

Upvotes: 1

Views: 556

Answers (2)

Kirill Bubochkin
Kirill Bubochkin

Reputation: 6353

The reason is that by convention for lambda last statement is an implicit return, so your first block is treated as something like this:

function a(): String {
    return while (true) {
        return "hello"
    }
}

Which basically can be read as "return the result of while block" and it is considered as Unit.

You can use anonymous function instead to bypass this convention:

val a = fun(): String {
    while (true) {
        return "hello"
    }
}

Upvotes: 4

TheOperator
TheOperator

Reputation: 6526

In a lambda that has a non-Unit return type, the last statement must be an expression. while is not an expression, and thus Kotlin infers that the code block

while(true){
    return@Callable "hello"
}

is meant to return Unit. The compiler does not do deeper analysis to infer that it's an infinite loop with a non-local return statement.

As such, you must make sure that the last line of your lambda is an expression of type String.

I once built a helper function for scenarios where a certain type is expected, but not a value:

fun <T> declval(): T = throw IllegalStateException("Code should be unreachable")

It is modeled after C++ std::declval(). So, your lambda could be:

val a:() -> String = a@ {
    while(true){
        return@a "hello"
    }

    declval<String>()
}

But it might be easier to understand if you just change the control flow of your infinite loop to result in one exit point (e.g. using break).

Upvotes: 1

Related Questions