I have a problem with calling suspending functions...
# getting-started
s
I have a problem with calling suspending functions from inside a lambda which is provided from an interface. More specifically, the interface has a function that looks like this
fun foo(lambda: Bar.() -> Unit)
. On one of the places where I call this, I happen to be inside a suspending function, where I want to be able to simply call on a suspend function like I normally would. This isn’t allowed since that lambda isn’t suspending, and I can’t make it suspending either. I see that this is solved in the vast majority of cases by having the function itself be
inline
which makes this possible, however I see that it’s not possible to define an interface function as inline. I’m sure this is something that others have faced as well, what are my options? I see that this is solved by defining them as extension functions to the interface like map is defined, but in my case I can’t do that either since the implementation of my interface wants to use some local variables when implementing this function.
j
How is the interface used? What does it represent? And more specifically who calls this lambda and how? If the lambda is called in-place then the interface should probably declare
foo
as
inline
in the first place. If it's not called in-place, then it doesn't matter whether you're calling
foo
from a suspend function or not, what matters is the caller of the lambda itself - it may rely on the fact that this lambda is called synchronously, blocking the thread. In that case you have no choice but to block in order to call your suspend function (e.g. using
runBlocking { ... }
, provided you're not on JS platform).
👍 1
e
your interface needs a
suspend fun foo(lambda: suspend Bar.() -> Unit)
or an inline
foo
if you want it to be callable with a suspending lambda. those are your options.
s
Yeah I don’t want it to be suspending itself, as it’s used many times from places where I don’t have/want to be in a suspending context. I would like it to be an inline
foo
as you say, but that’s specifically when I meet the
'inline' modifier is not allowed on virtual members. Only private or final members can be inlined
problem, and the IDE hint is to extract it as an extension function, which as I said before I can’t quite do since my implementation uses local values which I wouldn’t have access to in the extension function. I see that both of you suggest that foo should be inline in the first place, am I missing something that I’d need to do to not have the error that I mention above?
j
My bad, I missed the point about
inline
not being possible in an interface. Yeah the compiler cannot know what to inline without knowing the implementation up front, and the point of a virtual method is to not know the implementation up front.
my implementation uses local values which I wouldn’t have access to in the extension function
I thought you meant local variables (which are definitely ok in extension functions), but you're talking about private fields here, which indeed is a burden. One option is to provide a public API for the operations you want to do before and after calling the lambda, so you can use an inline extension function. Another option would be to provide a
suspend
overload of your function (probably with a different name) which you can call when you need your lambda to be suspending:
Copy code
interface IFoo {
    fun foo(lambda: String.() -> Unit)
    suspend fun fooSuspend(lambda: suspend String.() -> Unit)
}
A third option is to not care about it in your interface nor the implementation, and just block the current thread from inside the lambda when you need to do the suspending stuff
s
Awesome yeah I also thought of extracting the logic into two public fields which will enable me to make use of the extension function, but I like the other alternative as well. Thanks a lot for helping out! It’s a bit unfortunate there isn’t an even easier way to do this, I think due to this being a bit weird, as long as I can help it I’ll just try to not use this with suspending functions like here where executeQuery is suspending but I kept it outside of the lambda, but if I find a case where I must I’d probably go with the suspending/not suspending duo. That’s the easier to reason about as well imo.
Yeah, I’m just always wary of blocking like that, especially since I don’t feel 100% comfortable that I’d also not be breaking structured concurrency and/or have other unwanted side-effects that I wouldn’t be aware of. Always happy to take the safe path whenever possible 😄
👍 1