Данные и управление

Записи и номинальные данные

Как выбрать структурную запись, record, enum, объединение или newtype для данных предметной области.

«У этих значений одинаковые поля» и «эти значения обозначают одно понятие предметной области» — разные обещания. Поэтому в Топазе есть несколько форм данных. Сначала решите, какое различие должна сохранять программа, а затем выберите самую простую подходящую форму.

Запустите пример

Сохраните код в файле records-nominal-data.tpz:

TOPAZ
type DisplayValue = int | string

newtype UserId = int

enum Status {
    Active,
    Paused(string),
}

record User {
    id: UserId,
    name: string,
    status: Status = Status.Active,
}

function statusLabel(status: Status) -> string {
    match status {
        case Active => "active"
        case Paused(reason) => reason
    }
}

function display(value: DisplayValue) -> string {
    match value {
        case number: int => "number {number}"
        case text: string => text
    }
}

let before: User = User { id: UserId(7), name: "Ada" }
let after: User = User { ...before, status: Status.Paused("review") }
let preview = { name: "Ada", age: 36 }
let older = preview{ age: 37 }
let ready = display("ready")

print("{before.id.value()}:{before.name}:{statusLabel(before.status)}")
print("{after.name}:{statusLabel(after.status)}")
print("{preview.age}:{older.age}")
print("{display(7)}:{ready}")
BASH
topaz check records-nominal-data.tpz
topaz run records-nominal-data.tpz

Программа напечатает:

Вывод
7:Ada:active
Ada:review
36:37
number 7:ready

Выберите форму данных

ФормаКогда выбиратьСоздание и чтение
Структурная записьЛокальному значению достаточно известного набора полей{ name: "Ada", age: 36 }, затем value.field
Номинальная recordВажны имя предметной области и принадлежность объявлениюUser { ... }; поля, значения по умолчанию, именное обновление и шаблоны
enumЗначение является ровно одним вариантом из закрытого набораStatus.Active или Status.Paused(reason); выбор через match
Объединение A | BПодходит значение одного из уже существующих типовСужение с помощью литерального, конструкторного или типового шаблона
newtypeБазовому значению нужна отдельная предметная идентичностьUserId(7), затем снятие одного слоя методом .value()

User — номинальная запись. Если поле status пропущено, его значение по умолчанию вычисляется один раз, поэтому before получает состояние Active. Форма User { ...before, status: ... } создаёт новую внешнюю запись, сохраняет остальные поля, не меняет before и не запускает значения по умолчанию повторно.

Status — закрытое перечисление. При создании используется пространство имён (Status.Paused), а в шаблоне — короткое имя варианта (case Paused(reason)). Благодаря этому проверяющий может убедиться, что statusLabel обрабатывает все варианты.

DisplayValue — псевдоним объединения, а не новая оболочка времени выполнения. Типовые шаблоны в display сужают существующее значение до int или string и связывают его с именем. UserId, напротив, остаётся отдельной номинальной оболочкой, хотя его базовый тип — int.

preview — структурная запись: её совместимость определяется формой полей. Она обновляется как preview{ age: 37 }. Это другая операция, не номинальное обновление User.

Создание, обновление и разбор

Поля номинальной записи проверяются по имени и типу. Явно заданные поля вычисляются слева направо, после них пропущенные значения по умолчанию вычисляются в порядке объявления. Номинальное обновление допускает один начальный ...source, за которым идут заменяемые поля. Копируется только внешняя запись, поэтому изменяемые значения внутри полей остаются общими.

Шаблон case User { name } => сначала проверяет, что значение создано объявлением User, а затем читает выбранные поля. Неуказанные поля игнорируются, но хотя бы одно поле назвать необходимо. Структурная запись с такими же полями всё равно не становится User.

Частая ошибка

Форма JavaScript { ...value } не является обновлением структурной записи в Топазе. Для структурной записи используйте value{ field: replacement }, а для номинальной — Name { ...value, field: replacement }.

Не помещайте в тело записи синтаксис получателя из Rust. Неявных this, &self и &mut self нет. Если нужен метод-получатель, ограниченная форма Топаза записывается в отдельном блоке impl Name верхнего уровня и принимает обычный неизменяемый self по значению.

Точные границы

  • Записи, перечисления и новые типы объявляются на верхнем уровне модуля и имеют номинальную идентичность.
  • Параметры обобщённых номинальных типов инвариантны; при создании им нужен ожидаемый тип или другой разрешённый контекст вывода.
  • Сравнение, порядок и ключи коллекций для номинальных значений сохраняют не только содержимое, но и принадлежность объявлению.
  • Шаблоны перечислений используют короткие имена вариантов; полнота проверяется по ветвям без дополнительных условий.
  • В начале шаблона номинальной записи стоит простое имя без пространства имён, а пустой номинальный шаблон недопустим.
  • Конструктор newtype принимает ровно одно базовое значение, а .value() снимает ровно один слой.

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

Начните с урока Данные и управление. Псевдонимы и объединения описаны в разделе Типы, а полный разбор значений и связанные имена — в разделе Шаблоны и управление потоком.