«У этих значений одинаковые поля» и «эти значения обозначают одно понятие предметной области» — разные обещания. Поэтому в Топазе есть несколько форм данных. Сначала решите, какое различие должна сохранять программа, а затем выберите самую простую подходящую форму.
Запустите пример
Сохраните код в файле records-nominal-data.tpz:
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}")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 в текущий язык не входят.
Начните с урока Данные и управление. Псевдонимы и объединения описаны в разделе Типы, а полный разбор значений и связанные имена — в разделе Шаблоны и управление потоком.