Is it possible to make a function "optionally suspend"? To illustrate with an example, let’s take this function that wraps a callback and logs before and after:
fun withLogs(cb: () -> Unit) {
println("Before");
cb();
println("After");
}
Now I can call this function with a normal function, but not with a suspended function:
fun unsuspend () {
withLogs({ println("my func") }); // this is fine
}
suspend fun suspended() {
withLogs(suspend { println("my func") }); // this is not
}
Now I could create an explicit overload for this, but it would be exactly the same, except for a few suspend keywords:
fun withLogs(cb: () -> Unit) {
println("Before");
cb();
println("After");
}
suspend fun withLogs(cb: suspend () -> Unit) {
println("Before");
cb();
println("After");
}
Is it possible to write the function such that it copies the suspend-ness of the cb? E.g. this idea:
<S: suspend> fun withLogs(S cb: () -> Unit) { /* snip */ }
fun unsuspend () {
withLogs({ println("my func") }); // suspended
}
suspend fun suspended() {
withLogs(suspend { println("my func") }); // unsuspended
}
So far, the best workaround I’ve found is to use runBlocking
to delegate from the unsuspended version to the suspended version:
fun withLogs(cb: () -> Unit) = runBlocking(withLogs(suspend { cb() }))
but hoping that there’s something more elegant
>Solution :
If the function parameter is called in-place like here, the simplest way to do this (and how it’s done in most stdlib functions) is to make your function inline
:
inline fun withLogs(cb: () -> Unit) {
println("Before")
cb()
println("After")
}
This works because cb
is inlined here, so it doesn’t matter whether it’s declared suspend
or not, its content will be subject to the constraints on the call site.
You will be able to use suspend function calls in the lambda if you are in a suspending context:
suspend fun caller() {
withLogs {
delay(100) // suspend is ok because we're in a suspend caller
println("yeah!")
}
}
And it will correctly give a compile error when not in a suspend context:
fun blockingCaller() {
withLogs {
delay(100) // compile error
}
}
If the function is not called in place (and instead is passed around), then you’ll be subject to the constraints of the other functions you’re passing the callback to, and you may have to add a suspend
keyword on your parameter. This is more a conceptual constraint than a language limitation.