Потребляемые типы в Nova

Руководство для пользователя по работе с потребляемыми типами: связывания, передача владения, заём-представление, жизненный цикл.

TL;DR

consume x = Token.new(7)    // ✓ ownership binding
ro y = x                   // ✗ E_VIEW_BINDING_FORBIDDEN
consume y = x               // ✓ move (x dead, y owns)
ro v = x.release()         // ✓ consume via method
  • Владение передаётся через consume X = ….
  • Связывание-псевдоним запрещено внутри тела функции (let Y = X).
  • Заём-представление (view-borrow) разрешён ТОЛЬКО как параметр функции.
  • Каждое consume-связывание должно быть потреблено до выхода из области видимости.

Что такое потребляемый тип?

Потребляемый тип — это тип, чьи значения представляют владение неразделяемым ресурсом: файловым дескриптором, охраной мьютекса, буфером строителя (builder), сетевым сокетом. Значения нельзя копировать, им нельзя создавать псевдонимы или неявно отбрасывать; владелец обязан явно их потреблять.

Объявление:

type Token consume {
    val int
}

Ключевое слово consume в объявлении типа помечает все экземпляры как обязанные быть потреблёнными.

Правила связывания (D180)

Правило 1 — для consume-RHS обязательно consume X = …

ro t = Token.new(7)        // ✗ E_CONSUME_KEYWORD_MISSING
consume t = Token.new(7)    // ✓

Компилятор статически определяет, когда связывание получает значение потребляемого типа, и требует ключевое слово consume.

Правило 2 — let Y = consume_var запрещено в теле функции

consume sb = StringBuilder.new()
ro view = sb                       // ✗ E_VIEW_BINDING_FORBIDDEN

Создание псевдонима для обязательства потребления привело бы к висячей ссылке после того, как sb будет потреблён; связывание-псевдоним внутри тел функций запрещено.

Правило 3 — consume Y = X передаёт владение (move)

consume a = Token.new(11)
consume b = a               // move — a dead, b owns
ro v = b.release()         // ✓

После перемещения a считается потреблённым; его использование вызывает диагностику use-after-consume.

Правило 4 — заём-представление только через параметры функции

fn snapshot(t Token) -> int => t.val

consume t = Token.new(7)
ro s = snapshot(t)         // ✓ view-borrow (bounded by call)
ro v = t.release()         // ✓ caller still owns; consume here

Параметры функции потребляемого типа без ключевого слова consume — это представления (views) — ограниченные областью видимости вызываемой функции, никогда не убегают наружу.

Операции потребления

Обязательство потребления погашается любым из способов:

  1. consume X = X_old — перемещение в новое связывание (правило 3).
  2. X.method(...), где метод объявлен как consume @-метод.
  3. return X — возврат из функции.
  4. Неявный возврат — завершающее выражение тела функции это X (или цепочка вызовов с корнем в X; см. [M-73.1-fluent-return- implicit-consume]).
  5. Передача в consume-параметрf(X), где f принимает consume X.

Цепочки вызовов с возвратом получателя (-> @)

Метод, объявленный с типом возврата -> @, возвращает самого получателя (Plan 77, D132). Цепочки вызовов компонуют мутаторы:

consume sb = StringBuilder.new()
ro s = sb.append("a").append("b").as_str()   // chain + consume

Когда такая цепочка является завершающим выражением тела функции, обязательство потребления корня цепочки погашается неявным возвратом (M3, Plan 73.1 V3).

Диагностики

КодТриггер
E_CONSUME_KEYWORD_MISSINGlet X = ctor(), когда ctor возвращает consume-тип
E_VIEW_BINDING_FORBIDDENlet Y = consume_var в теле функции
W_CONSUME_KEYWORD_UNNECESSARYconsume X = …, когда правая часть не consume-типа
D133-not-consumedвыход из области видимости с неудовлетворённым обязательством
D133-use-after-consumeиспользование потреблённого связывания

Все диагностики несут машинно-применимые предложения (Plan 50 D102).

Ссылки

  • spec/decisions/05-memory.mdD131 (аффинная семантика), D180 (синтаксис связывания)
  • docs/plans/73.1-consume-binding-syntax.md — статус Plan 73.1
  • docs/dev/migration/d180-binding-syntax.md — руководство по миграции