Non-local return from the block of inlined higher-order function

I can’t understand why test1 function causes the build to fail with a compilation error. I’ve been working on this problem for almost 3 hours, and I can’t figure out the puzzle here.

I want to understand

  • why test1 failed to compile?
  • why test3 works while test1 failed?

fun test1(): Int {
    aroundBlockWithUnitReturnType {
        return 0
    }
} // <- error : A 'return' expression required in a function with a block body ('{...}')

// you might probably wonder why test3 and not test2;
// well, because I've edited the question and I don't want to 'break' old answers!
fun test3(): Int {
    aroundBlockWithTypedReturn {
        return 0
    }
}

inline fun aroundBlockWithUnitReturnType(block: () -> Unit) {
    println("before")
    block()
    println("after")
}

inline fun <T> aroundBlockWithTypedReturn(block: () -> T): T {
    println("before")
    val result = block()
    println("after")
    return result
}

Just because you function is inline doesn’t mean the block will run. So the return statement is not guaranteed to run.
You can use kotlin contracts to workaround that.
I’m not sure why the test3 is ok, the compiler is probably inferring the result type of the function to be Nothing.
Try this test3 to see if you get the same error:

fun test3(): Int {
    aroundBlockWithTypedReturn<Int> {
        return 0
    }
}
1 Like

Yeah, you’re right, I’m getting the same error.

I don’t understand yet what’s the problem here. Would you please clarify a bit of what’s wrong here, and how can I use contracts to work around this and make test1 complies?

Imagine your inline function is:

inline fun aroundBlockWithUnitReturnType(block: () -> Unit) {
    println("before")
    if (false) block()
    println("after")
}

The return in test1() will never be reached.

As for the contract, take a look at the run implementation:

public inline fun <T, R> T.run(block: T.() -> R): R {
    // This tell the compiler that you block will always runs, once
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}
1 Like

Alright, now I get it. Thank you.

One important thing to note about contracts is that the compiler doesnt check that they are true. That means that you are responsible for ensuring that the function actually gets called exactly once if that is what the contract says.

Yeah; it’s strange that a language so hot on safety of various kinds doesn’t actually check contracts at all. Presumably that’s why they aren’t yet available for the rest of us.

Perhaps if/when the compiler gets smart enough to check them, it’ll be able to infer them as well, and we wouldn’t need to write them out explicitly? (Hey, I can hope…)

Kotlin will probably never infer contracts, only check them at some point. Otherwise making a small change in a function could have quite severe effects somewere else. It’s a similar reason to why inline or tailrec isn’t infered.

There is really no technical reason why Kotlin couldn’t allow these kinds of returns though. It could be implemented behind the scenes using exceptions.

In general, there are a lot of very nice improvements Kotlin could implement if they decided that they wanted to go beyond what is native functionality in the JVM.