Корутины

Корутины - это функции, логически похожие на обычные функции, но с тем отличием, что они могут приостанавливать своё выполнение и потом снова его возобновлять. При этом их внутреннее состояние - аргументы, локальные переменные, временные значения и т. д. сохраняются после преостановки и восстанавливаются при возобновлении.

Существуют следующие виды корутин:

Объекты-корутины, это объекты специальных классов (генерируемых компилятором), которые порождаются вызовом функции-корутины. Внутри объект хранит внутренее состояние корутины. С помощью оператора if_coro_advance можно возобновлять выполнение объекта-корутины и получать результаты этого выполнения. Объекты-корутины некопируемы, но разрушаемы (имеют деструктор) и могут свободно перемещаться. Также они могут быть сравнены на равенство, при этом каждый объект-корутина уникален и равенство истино, только если объект сравнивается сам с собою.

if_coro_advance

Оператор if_coro_advance позволяет работать с объектами корутин - возобновить исполнение и получить результат.

Данный оператор выполняет следующее: проверяет, что переданный объект-корутина не завершился, если нет, то возобновляет её исполнение. Далее снова проверяется, завершилась ли корутина после возобновления. Далее способ работы различается от типа корутины.

В случае генераторов, если генератор ещё не завершился, получается результирующее значение (порождённое оператором yield) и привязывается к указанной переменной. Далее управление предаётся указанному блоку кода.

В случае асинхронной функции управление указанному блоку кода передаётся только если после текущего возобновления эта асинхронная функция завершилась и тем самым вернула результат.

Пример:

fn generator SomeGen() : i32;

fn Foo()
{
    auto mut gen= SomeGen();
    if_coro_advance( x : gen )
    {
        // Сюда передастся управление, если генератор ещё не завершился. В переменной "x" хранится результат выполнения генератора.
    }
    else
    {
        // Сюда передастся управление, если генератор уже завершился или завершился после возобновления исполнения.
        // Ветвь "else" у "if_coro_advance" является опциональной.
    }
}

Переменная внутри if_coro_advance может иметь модификаторы ссылочности/изменяемости. Если даже результат корутины не ссылочный, а переменная в if_coro_advance объявлена как ссылочная, то будет произведена превязка значения к ссылке, на подобие того, как это осуществляется в операторе with.

Внутреннее представление

Функция-корутина разбивается компилятором на три отдельных функции. Первая - собственно начальная функция корутины, которая конструирует объект корутины - выделяет под него память (из кучи) и размещает в ней аргументы. Кроме этого создаются функции возобновления корутины и её разрушения, указатели на которые размещаются в объекте корутины. При возобновлении или разрушении объекта-корутины вызываются эти функции. Поскольку эти функции вызываются не напрямую, а через указатель, то возможно хранение в одной переменной типа корутины объектов, порождёнными различными порождающими корутины функциями.

Все локальные переменные, ссылки, промежуточные значения и т. д. храняться в памяти, выделенном для объекта-корутины.

Объект корутины также хранит в себе номер точки, на которой корутина в данный момент приостановлена. Функции возобновления и разрушения передают управление коду, соответствующему данной точке.

Каждая корутина при этом имеет как минимум две точки останова - начальную и конечную. В начальной точке объект корутины пребывает сразу после создания. В конечной точке он пребывает по завершении корутины.

Классы объекта корутины содержат в себе только одно (скрытое) поле - указатель на блок памяти. В этом блоке содержатся адреса функций возобновления/разрушения, номер точки останова, место под результат и память под аргументы/локальные переменные/временные значения. В целом содержимое этого блока не доступно программисту и доступ к нему осуществляется только в недрах компилятора.

Упомянутое выше выделение памяти из кучи может в некоторых случаях оптимизатором компилятора заменено на выделение памяти из стека - если, например, объект корутины создаётся, используется и разрушается в одной функции. Но в общем случае данная оптимизация не гарантирована.

Разрушение объекта корутины

Допустимо разрушить объект корутины, когда он ещё не завершился и всё ещё хранит в своей памяти локальные переменные и аргументы. При этом эти переменные будут разрушены (будут вызваны деструкторы) должным образом. Это осуществляется деструктором типа корутины через вызов по указателю функции-разрушения корутины.