OxL Справочник OxL
РусскийEnglish

4.3 Конструирование и конструкторы

Литерал. T{ f1: e1, f2, ..base }:

  • поля без значения по умолчанию ДОЛЖНЫ быть заданы, иначе ошибка компиляции;
  • f2 — сокращение для f2: f2;
  • ..base берёт остальные поля из base (перемещая или копируя их);
  • литерал допустим, только если в точке вызова видимы все поля T и у T нет атрибута @sealed.

Позиционный литерал. T(e1, e2, …) — поля в порядке объявления; допустим при тех же условиях и если у T не больше 4 полей без значения по умолчанию.

Именованные конструкторы объявляются в impl словом init:

oxl
@sealed data Account {
  id: AccountId
  owner: UserId
  balance: Money
  invariant balance >= Money.zero()
}

impl Account {
  init new(owner: UserId) -> Self =>
    Self{ id: AccountId(cluster.ids.next()), owner, balance: Money.zero() }

  init open(owner: UserId, deposit: Money) -> Self | TooSmall {
    if deposit < MIN_DEPOSIT { fail TooSmall(deposit) }
    Self{ ..Self.new(owner), balance: deposit }
  }
}

let a = Account.new(me)
let b = Account.open(me, Money.rub(500))?
  • init name(params) -> Self | E — ассоциированная функция с доступом ко всем полям; возвращаемый тип ДОЛЖЕН быть Self или объединением, содержащим Self.
  • @sealed запрещает литерал вне impl типа; конструирование только через init.
  • T.default() существует, если у всех полей есть значения по умолчанию, или при @derive(Default).
  • Значения уточнённых типов в литерале подчиняются §3.6: User{ name: "Ann" } — проверка при компиляции; User{ name: Name.from(s)? } — во время выполнения.
  • Встроенные поля (§4.7) задаются плоско (Dog{ name: "Rex", breed }) или целиком по имени типа (Dog{ Animal: a, breed }).

Вызов типа. T(args) разрешается так: если позиционный литерал T допустим в точке вызова — это литерал; иначе, если у T есть init new, — вызов T.new(args); иначе — ошибка компиляции. Для distinct-типа T(x) — конструктор, для шардированного актора — ссылка ActorRef.

Уничтожение. Значение уничтожается при выходе из области видимости владельца в порядке, обратном объявлению. Пользовательский деструктор — impl Drop for T { var fn drop() { … } }; Drop реализуется только явно.

Деструкторы синхронны. Тело drop ДОЛЖНО быть без эффектов и без точек приостановки; допускаются только неблокирующие вызовы освобождения локальных ресурсов ОС, помеченные @nonblocking (закрыть дескриптор, освободить память). Асинхронных деструкторов нет.

Асинхронная очистка — явный метод и defer:

oxl
@must_close
data Conn { sock: Socket; open: bool = true }

impl Conn {
  move fn close() -> () | NetError  net {
    shield { sock.sendGoAway()? }           // отмена не прерывает завершение
    open = false
  }
}

impl Drop for Conn {
  var fn drop() {
    if open {                                // close() забыли: страховка без сети
      sock.closeLocal()
      log.warn("Conn dropped without close()")
    }
  }
}

fn talk(addr: Addr) -> () | NetError  net {
  let c = Conn.dial(addr)?
  defer c.close()?                           // ошибка close() возвращается из функции
  c.send(HELLO)?
}
  • @must_close на типе: если значение может выйти из области видимости без вызова move-метода, помеченного как закрывающий (по умолчанию close, иначе @must_close(by: имя)), или без передачи владения, — ошибка компиляции must-close.
  • defer e? внутри функции с ошибками: ошибка e возвращается из функции, если функция ещё не возвращает другую ошибку; иначе пишется в лог.
  • Синхронный Drop остаётся страховкой на случай паники и отмены: он освобождает локальные ресурсы и пишет предупреждение, но в сеть не ходит.

bad-init, missing-field, must-close, positional-literal, sealed-literal

См. также

Исходник: docs/spec/oxl-standard.md