What is restricted suspension in Kotlin?
Tags: kotlin, programming, computerscience
Published: 2021-02-26 17:35:14

{% cta https://blog.zachklipp.com/what-is-restricted-suspension-in-kotlin/ %} This post has moved to blog.zachklipp.com. {% endcta %}

---

###### _Why can't I call suspend functions in `sequence`?_

[`sequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/sequence.html) lets you create [sequences](https://kotlinlang.org/docs/sequences.html) using a syntax that looks kind of like Python generators, using a feature of coroutines that you don’t normally run into when you’re using coroutines for threading or normal async programming.

_Be careful not to confuse the coroutine-based sequence builder, `sequence`, with its more functional sibling, [`generateSequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/generate-sequence.html)._

## What does "Kotlin Coroutines" even mean?

Typically when you think coroutines, you’re actually thinking coroutines + [jobs](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/) + [dispatchers](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/) + etc. But dispatchers and even jobs are not part of the core coroutine support in Kotlin. Most of the coroutine goodies are actually in a completely separate library: [`kotlinx.coroutines`](https://github.com/Kotlin/kotlinx.coroutines). The [core coroutine support](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/) in Kotlin is basically just the [`Continuation`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/-continuation/) type, a low level API to get a continuation out of a suspend function, and very low-level support for `CoroutineContext`s. A continuation is basically just a callback that the compiler generates for you from the body of a suspend function. The stdlib is not opinionated about what kinds of things you can build with these fundamentals. They can be useful for general async multithreaded programming - that’s what `kotlinx.coroutines` is for – but they can also do other tricks. 

You’ll notice that `sequence` is in the stdlib and doesn’t use anything from `kotlinx.coroutines`.

## Laziness

The `sequence` builder doesn’t create a general coroutine scope that would, e.g., allow you to go off and do other work in a thread. If that’s what you want then you don’t want a [`Sequence`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence/), you want a [`Flow`](https://kotlinlang.org/docs/flow.html). A sequence is simply a regular [`Iterable`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-iterable/) (yep, the one from Java) that just has the contract of calculating itself lazily (something iterables can do, but usually don’t - see [`List`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/), [`Set`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-set/), etc). The sequence builder doesn’t want to let you do just any asynchronous work. Imagine if it did - if you awaited a `Deferred`, for example, you’d need to potentially block the thread to wait for the result to be available. But all that blocking and `await` stuff is specific to the `kotlinx.coroutines` library. The sequence builder needs to be very strict - the only type of asynchrony it supports is to suspend until the next call to next on its [`Iterator`](https://kotlinlang.org/docs/iterators.html) – it doesn't know how to schedule arbitrary coroutines. [`Yield`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/-sequence-scope/yield.html) is the only suspend function it supports because the only reason to suspend is to wait for the next element of the sequence to be requested.

## Concurrency vs Parallelism

No exposition about coroutines would be complete without reminding the reader that concurrency ≠ parallelism. Parallelism means two things are happening at the same actual time. When you blink both eyes at the same time, that's parallelism. Concurrency, on the other hand, is more subtle, and simply means that the ordering of two events (or sequences of events) is undefined. It's kind of like merging onto a freeway – you have to enter between two vehicles, but it doesn't matter which two.

The sequence coroutine is concurrent, but not parallel, because while its code is interleaved with the code pulling from the sequence, the two never actually execute at the same time.

## The sequence of a sequence

The coordination between a `sequence` coroutine and its `Sequence` is much tighter than you usually see with `kotlinx.coroutines` code. The call to yield indicates a new value is produced, and thus must provide that value to the sequence builder. It wouldn’t make sense to suspend any other way, since the sequence builder needs to know the next value on each suspension. When you pull from a `Sequence`, here's what the sequence builder does:

1. It doesn’t do anything until the first sequence element is requested via `Iterator.next()`.
2. When requested, it runs the coroutine until it suspends. Because the only way to suspend a sequence coroutine is to provide a value with yield, the suspension is guaranteed by the compiler to provide the requested value.
3. Returns the yielded value from `next()`, then goes back to 1.

_Actually `next` and `hasNext` can both resume the coroutine, I just used `next` for brevity._

So the only way to provide an element is to suspend, and the only reason to suspend is to provide an element. But `suspend` is just a function modifier – does this mean that `sequence` has to throw at runtime if you try hopping threads?

## Restricted suspension

The core coroutine support in the Kotlin stdlib provides a special annotation that can coroutine builders can use to restrict the types of suspension that can be done in their coroutines: [`@RestrictsSuspension`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/-restricts-suspension/). It works by only allowing the suspend functions defined on the specified receiver type to be invoked. Calling any other suspend functions gives a compiler error.

The sequence builder's lambda has a receiver type of `SequenceScope`. This class [is annotated](https://github.com/JetBrains/kotlin/blob/1bb31bb36f9720d6637152757b2e7631c807d6e8/libraries/stdlib/src/kotlin/collections/SequenceBuilder.kt#L59-L61) with `@RestrictsSuspension`:

```kotlin
@RestrictsSuspension
@SinceKotlin("1.3")
public abstract class SequenceScope<in T> // …
```

You can use `@RestrictsSuspension` for other things too, in your own code - any time you want to be very specific about what it means to “suspend” a particular coroutine.