Native records
Рішення плану 11, step 31A (2026-10-07). Воно окреслює форми record з OTP 29,
що виходять за межі звичайних кортежних record, і фіксує їхнє подання, операції
та помилки. Step 31B реалізував комірки runtime і CLAUSE_record_v1; 31C
компілює локальні native records, 31D — кваліфіковані та імпортовані форми з
-export_record/-import_record, 31E — анонімні форми. Кортежні record
описано в термах.
OTP 29 позначає native records як експериментальні. Наведені нижче факти взято
із зафіксованих вихідних кодів maint-29 (erl_lint, erl_expand_records,
v3_core, beam_core_to_ssa, erts/emulator/beam/erl_record.c, utils.c,
erl_printf_term.c) і живих перевірок на OTP 29.1.1.
Обсяг
Вибрано всі три форми, що виходять за межі кортежних record. Вони мають спільне подання, тож окремого рішення щодо подання для них не потрібно:
| Форма | Синтаксис | Крок |
|---|---|---|
| Native record, локальний | -record #r{...}., #r{...}, X#r.f, X#r{...}, зразки, is_record/1,2,3 | 31C |
| Кваліфікований та імпортований | #m:r{...}, X#m:r.f, X#m:r{...}, зразки; -export_record, -import_record | 31D |
| Виведений (анонімний) | X#_.f, X#_{...}, зразок #_{...} | 31E |
Не вибрано: модуль рефлексії records (бібліотека, разом із бібліотечною
роботою step 39), term_to_binary (RECORD_EXT) і гаряче оновлення коду (D01).
Виклики records:* лишаються діагностикою unknown module.
Подання
- Дескриптор. Кожне визначення native record компілюється в один незмінний
abi::v1::RecordDescriptorпоруч із дескриптором свого модуля: визначальнийModuleDescriptor, слоти атомів для назв модуля і record, прапорець експорту (-export_recordпід час компіляції) і слоти атомів назв полів у порядку визначення. Слоти атомів розв'язуються через зареєстровані атоми модуля, як і назвиFrameDescriptor. Програми статичні (без вивантаження), тож дескриптор живе стільки ж, скільки програма; інший модуль пакета посилається на нього за символом. Значення за замовчуванням компілюються в кожне місце конструювання і ніколи не зберігаються. - Екземпляр. Комірка купи виду
native_record: заголовок (лічильник1 + n), невідстежуване слово дескриптора, потімnвідстежуваних значень у порядку визначення. Record без полів — комірка з двох слів. - Дескриптор фіксує модуль, назву, прапорець експорту і список полів під час конструювання, як і захоплене визначення в OTP. Кожна операція після конструювання читає дескриптор екземпляра, а не код, який її виконує.
- Копіювання, збирання сміття, обхід і перевірка трактують комірку як кортеж, перше слово корисного навантаження якого не відстежується. Допуск вимагає дескриптора, зареєстрованого модулем того самого runtime.
Операції
M — поточний модуль. Усі помилки мають клас error; вмістом badrecord є
значення, що спричинило помилку, за винятком невдалого зовнішнього
конструювання.
| Операція | Приймає | Невдача |
|---|---|---|
Локальне конструювання #r{...} | Завжди (визначення локальне) | Невідоме поле або відсутнє значення — помилки компіляції |
Зовнішнє конструювання #m:r{...} (також імпортований #r{...}, також #M:r) | m:r визначено в пакеті й експортовано | {badrecord, {m, r}}; потім {badfield, {{m, r}, F}}, {novalue, {{m, r}, F}} (badfield має перевагу) |
Локальний доступ X#r.f | Native record з назвою r (модуль і прапорець експорту не перевіряються, як у runtime OTP) | {badrecord, X}; поле відсутнє — {badfield, {{Mod, r}, f}} |
Зовнішній доступ X#m:r.f | Модуль m, назва r, експортований | {badrecord, X}; {badfield, ...} |
Анонімний доступ X#_.f | Будь-який native record (експорт не перевіряється, як у runtime OTP) | {badrecord, X}; {badfield, ...} |
Локальне оновлення X#r{...} | Модуль M, назва r | {badrecord, X}; невідоме поле — {badfield, ...} |
Зовнішнє оновлення X#m:r{...} | Експортований, модуль m, назва r | так само |
Анонімне оновлення X#_{...} | Експортований або модуль M | так само |
Локальний зразок #r{...} | Модуль M, назва r, кожне перелічене поле присутнє | немає збігу |
Зовнішній зразок #m:r{...} | Модуль m, назва r; експортований, якщо перелічено поле | немає збігу |
Анонімний зразок #_{...} | Будь-який native record; експортований або модуль M, якщо перелічено поле | немає збігу |
- Конструювання обчислює задані поля в порядку вихідного коду (значення за замовчуванням — літерали); оновлення спочатку обчислює record, потім нові значення в порядку вихідного коду, на відміну від оновлення кортежного record. Порожні оновлення все одно виконують перевірку.
- Guard: лише доступ до поля (невдача робить guard невдалим) і
is_record. Конструювання в guard даєcreating a record in a guard is only supported for tuple records; оновлення —illegal guard expression. is_record(X)істинне лише для native records.is_record(X, r)з локальним нативнимrперевіряє модульMі назвуr; зr, імпортованим зm, — модульm.is_record(X, m, r)з атомами перевіряє модуль і назву; з цілим числом це перевірка кортежу.- Native records не є кортежами:
is_tupleдає false; сервіси елементів і розміру їх відхиляють.
Правила часу компіляції
Повідомлення дотримуються erl_lint:
- Значення за замовчуванням мають бути літералами після згортання констант
(числа, атоми, рядки,
[], cons-комірки/кортежі/map із них, binary лише з рядків):illegal default value for field a in native record r. - Локальне конструювання без значення для поля без значення за замовчуванням:
field a is not initialized in native record r; невідоме поле:field zz undefined in record r. Невідомі поля в доступі, оновленні та зразках приймаються (OTP лише попереджає); вони завершуються помилкою під час виконання. - Індекс
#r.a,record_info/2, ініціалізатори_ = Vі типізовані уточнення полів доступні лише для кортежних record, з повідомленнями OTP. - Назва належить або кортежному record, або native record, або імпортованому
record; конфлікти та неправильні форми
-export_record/-import_recordє помилками.-export_recordмає передувати визначенням функцій. - Зовнішні форми, що називають модуль поза пакетом, невизначений або
неекспортований record, компілюються і завершуються помилкою під час
виконання, як в OTP. Зовнішнє конструювання розв'язується відносно пакета під
час компіляції: імпортувальний модуль сам понижує літеральні значення за
замовчуванням визначального модуля і посилається на його експортований
дескриптор; невідомі поля викликають
badfieldраніше, ніж відсутнє значення викличеnovalue(ErrorReason21).
Друк і порядок
erlang:display/1друкує#m:r{a=1,b=2}: без пробілів, поля в порядку визначення. OTP друкує поля в порядку індексів таблиці атомів, який залежить від порядку створення атомів і не відтворюваний; це зафіксована відмінність, як і для ключів map.- Порядок термів: кортеж < native record < map. Record порівнюються за модулем,
назвою, прапорцем експорту (false спочатку), кількістю полів, назвами полів у
порядку визначення, потім значеннями в порядку визначення.
=:=додатково вимагає однакового порядку полів;==порівнює значення чисельно (компілятор OTP згортає деякі==над record до=:=; дотримуємося правила runtime).
Збережені відмінності
| OTP | Clause |
|---|---|
Порядок полів у display відповідає індексам атомів | Порядок визначення |
| Зовнішнє конструювання для незавантаженого модуля завершується помилкою, доки його не завантажено | Кожен модуль пакета завантажується під час старту; модуль поза пакетом завжди дає помилку |
Компілятор може згорнути == між record до =:= (#r{a=1} == #r{a=1.0} дає false) | == з runtime (true) |
| Невідомі поля в доступі/оновленні/зразках, кваліфіковане конструювання у власному модулі без значень, невикористані record або record, визначені в заголовках: попередження | Без попереджень |
Clause