Using coroutines to rewrite recursive functions into iterating ones

⚓ Rust    📅 2026-01-31    👤 surdeus    👁️ 2      

surdeus

Hello everyone!

Currently, Rust has an unstable feature called coroutines. A coroutine is a special type of closure that can either return completely (dropping all its local variables and without a possibility to resume it), or return temporarily (yield), in that case all local variables are preserved and a coroutine can be resumed later. It's similar to asynchronous function futures, with the difference that async functions yield when the job cannot be finished now (but may be able to finish later) because it requires some external event (e.g. an arrival of data from server, a network connection) that has not happened yet, while coroutines yield either when the job is partially done, or some new data is requested. A coroutine interface (core::ops::Coroutine) is similar to the future interface (except there is no "context object" like in core::future::Future, a yielding coroutine may provide some value and some value of an argument type may be expected on every resuming) and to the iterator interface (except that a coroutine may be non-Unpin and therefore Pin<&mut Self> is required instead of &mut Self, a value may be requested on every resume and a value may be returned when "iteration" completes).

If a project allows using unstable features (or when coroutines are stabilized), it can use coroutines to avoid recursion. The main reason why a project may want to avoid recursion is that a recursive function may allocate a hard-to-predict amount of memory on the stack and cause a stack overflow. To avoid this, a recursive function may be rewritten to an iterating one, which means all the necessary memory will be allocated on the heap. One example of a recursive algorithm is finding all permutations of a given sequence. A recursive algorithm may look like:

Recursive version (click for more details)

An iterating version may look like:

Iterating version (click for more details)

In the iterating version, a caller creates a "virtual stack" on the heap. Each time a permutation algorithm function would otherwise call itself, it yields. A caller then handles invocation of the permutation coroutine with a necessary argument and resumes the previous coroutine with the value that would be returned by invocation of the recursive function.

1 post - 1 participant

Read full topic

🏷️ Rust_feed