Why coroutineScope { ...} function is not implicit inside suspend functions?

I am just wondering what would be the downsides of having an implicit coroutineScope inside suspend functions. I mean, instead of:

suspend fun scoped() = coroutineScope { coroutines launched here }

one could just write:

suspend fun implicitlyScoped() {  coroutines launched here }

suspend functions have access to coroutineContext anyway. Is it so costly to create new scope (job), even if it is not needed?

This need to wrap a lot of things into coroutineScope { … } is a bit tiring:

flow { 
   coroutineScope { 
      only here I can use some coroutines to make flow collect from multiple sources in parallel
   }
}

The first issue with this proposal is that CoroutineScope is part of the kotlinx-coroutines library and not in the standard library.

Moreover, to give you an example, I am heavily using coroutines without the kotlinx-coroutines library and having to construct an unused CoroutineScope for every call is an unacceptable overhead in my opinion.

2 Likes

Wrapping code in coroutineScope() means you are explicitly saying this function should suspend until all the child coroutines complete. If you don’t want the function to suspend (maybe this is a utility function that will be called from the calling function multiple times to start multiple coroutines) then your suggestion would break things.

Since enclosing function is suspending, it is obvious that we are going to suspend here. And if there are coroutines launched inside it is IMHO is also obvious, that I want to suspend until they all finish.

Main problem may be indeed performance. Maybe a new Scope is not needed, because I have no coroutines launched inside. Maybe I want to just switch context. Or establish a supervisor scope instead of an ordinary one.

However main selling point of coroutines seems to be simplicity + correctness. That is why I am looking, how existing syntax could be simplified.

It’s not obvious to me that everyone will want this behavior all the time.

coroutineScope {
    for (i in 1..10) {
        startSomething(i)
    }
}

suspend fun CoroutineScope.startSomething(i: Int) {
    delay(i)
    launch { ... }
}

It’s contrived, but that’s a function that suspends and also launches a new coroutine, but we want the function to return immediately. The outer coroutineScope() will wait until all of the child coroutines are finished.

Making suspend function an extension on CoroutineScope will grant You an IDE warning already, as it is pretty ambiguous. One should decide, whether he/she wants to suspend until everything is finished or return immediately and continue working.

You can always (and IMHO should) place this delay outside second function, so that second function is not suspending.

It isn’t so obvious, I wrote a library without using any con kotlinx.coroutine stuff.

At the same time, may kotlinx.coroutine's methods don’t require a CoroutineScope.
A coroutine scope is a marker for a fork-join algorithm, using the structured concurrency. You can take a look here:

Yes, I know what coroutineScope is for. I just had a thought, that it could be default / implicit behaviour of any suspend function. So that one would have less boilerplate to write those fork-joins and they would feel more natural.

Nevermind, I realize, that it is not such a good idea in quite a few use cases.

Maybe this is the answer that are you looking for

1 Like

Yes, I had the same thoughts, that jcornaz was mentioning in referenced issue. Thanks!