Ориентация главы
Четыре главы xruntime завершаются. Семейство typed-контейнеров (xtypedarray/xtypedstack/xtypedlist/xtypedtree/xtypedset/xtypeddict) разделяет один контракт значений времени исполнения поверх шести структур данных: заимствованные неизменяемые описания типов, ведомые описаниями семейства операций над значениями, управляющие жизненным циклом значений (копия/перемещение/сравнение/хеш/разрушение — всё через таблицу описаний; факты о типах главы 114, исполненные контейнерами), единые глаголы владения (Set/Add/Push копирует, Take/Pop перемещает, SetTake атомарно переносит при отказе, Clone глубоко копирует), единая атомарность отказа (многошаговые операции идут в рабочем хранилище до фиксаации; отказ не ломает видимого состояния). Инженерные моменты: расширение переиспользует внутренние быстрые пути контейнеров XRT напрямую (базовые реализации не копируются — typed-слой есть «ведомая типами оболочка»; массив int64 работает так же быстро, как массив главы 14); операции над двумя контейнерами требуют равенства указателей описаний (одного ID/имени мало — жизненный ABI и частный контекст невзаимозаменяемы). Восемнадцать глав тома 11 здесь закрываются.
117.1引入
Зачем typed-контейнеры? C-контейнеры глав 14–23 (xarray(int64) и др.) — типизация на этапе компиляции — быстро, статично, но тип вмерзает в момент компиляции. Сценарии языка-хозяева (скриптовые массивы, JSON, загружаемый назад в память, обмен данными плагинов) требуют контейнеров с типами времени исполнения: «тип элементов этого массива — то описание по имени app.Counter из реестра» — один код контейнеров, диспетчеризация операций над значениями по описанию во время исполнения. Наивный подход — массив void* плюс разбросанные обратные вызовы — приведения в каждой операции всюду; typed-слой концентрирует диспетчеризацию в таблице описаний: один код контейнеров, всё поведение типов — в xrttype.
Отношение к динамическим значениям xvalue главы 31: xvalue — «одно динамическое значение» (самоописывающееся размеченное объединение); typed-контейнеры — «наборы однотипных элементов» (описание на уровне контейнера, ноль накладных расходов метки на элемент — массив int64 это непрерывные int64, а не массив Value). Они дополняют друг друга: Value хорош для гетерогенных деревьев (JSON), typed — для однородных наборов (скриптовые массивы/таблицы полей объекта).
117.2概念
Общий контракт: шесть семейств, одно лицо
| Глагол | Семантика |
|---|---|
Set/Add/Push/Insert | копирует исходное значение — контейнер владеет новым, исходное остаётся у вызывающего |
SetTake | атомарный при отказе перенос — при успехе исходное восстанавливается в пустое |
Take/Pop | перемещает в инициализированный выход — контейнер больше не владеет |
Remove/Clear/Unit | один Drop на каждое владеемое значение |
Clone/Merge/алгебра множеств | глубокая копия — без скрытого разделяемого владения |
Легальность адресов: API, принимающие адрес полного типизированного значения, допускают «внешнее значение или точный живой слот этого контейнера» — адреса, указывающие в структуру контейнера/метаданные/запасную ёмкость/паддинг/середину слота, отвергаются до чтения или записи; выход и источник перемещения обязаны лежать вне памяти контейнера (контейнерная версия защитной проверки).
Особые грани шести семейств
- array (непрерывное хранение): фиксированная/переменная длина, доступ по индексу, семейство Concat/Equals.
- stack (LIFO): Push/Pop/Peek.
- list (разреженные целочисленные ключи): вставка/удаление, итерация по порядку — форма скриптового списка.
- tree (упорядоченные родовые ключи): ведомое сравнением упорядоченное отображение — типу ключа нужны операции сравнения.
- set (уникальные значения): хеш-или-порядковая дедупликация — типу ключа нужны хеш/сравнение.
- dict (текстовые ключи): словарь со строковыми ключами — нагрузка узла динамических полей главы 115 (
xrtdynamicfields) — это он и есть.
Равенство указателей описаний: почему одного ID мало
Буквально из контракта: «операции с двумя typed-контейнерами требуют, чтобы описания типов элементов были одним и тем же указателем. Одинаковый ID, имя, размер или таблица операций недостаточны, чтобы доказать взаимозаменяемость жизненного ABI и частного контекста двух описаний.» Два модуля статически объявили «одинаковый» тип? Указатели разные — взаимодействия нет: встроенные типы XRT и типы, регистрируемые хозяином, должны использовать процессно-канонические описания (тот же указатель, что возвращает xrtTypeInt64()). Это правило уничтожает опасность «содержимое одинаковое, а место объявления разное» (неявный раскол частного контекста/жизни).
Переиспользование, а не копия
Расширение переиспользует внутренние быстрые пути контейнеров XRT напрямую — непрерывное хранение xtypedarray и есть внутренняя структура array главы 14; typed-слой добавляет входы, «диспетчеризирующие операции над значениями через таблицу описаний». Выгода двусторонняя: производительность (быстрый путь push int64 не замедляется под typed-оболочкой) и сопровождение (один код алгоритмов контейнеров — идея пул-изации главы 22 в ещё одной форме, «переиспользование реализаций»).
Атомарность отказа (общая)
При отказе выделения/инициализации значения/копии/перемещения: публично видимые элементы, ключи, порядок, владение не меняются — многошаговые операции (Clone/Merge/алгебра множеств) сперва завершаются в отдельном рабочем хранилище и фиксируются один раз; очистка при отказе уничтожает только успешно построенные в этот раз значения, сохраняя нижележащую ошибку как причину. Финальная категория OOM всегда XERR_MEMORY. Это продолжает дисциплину атомарности операций Value главы 31 и контейнеров главы 18 — typed-слой не расслабляется из-за «динамичности».
Место typed-контейнеров в четырёх главах xruntime
117.3示例
Первая полная программа: полный набор операций typed-массива
Программа ниже из examples/runtime/typed_array — цикл Init/Push/Clone/Concat/Equals/IndexOf:
#include <stdio.h> #include <xruntime.h> /* 展示类型数组的值复制、查询和自动生命周期。 */ int main(void) { xtypedarray Values; xtypedarray* pCopy; xtypedarray* pJoined; int64 arrInput[] = { 7, 11, 13 }; int64 iNeedle = 11; if ( !xrtTypedArrayInit(&Values, xrtTypeInt64()) ) { return 1; } for ( size_t i = 0; i < sizeof(arrInput) / sizeof(arrInput[0]); i++ ) { if ( !xrtTypedArrayPush(&Values, &arrInput[i]) ) { xrtTypedArrayUnit(&Values); return 2; } } pCopy = xrtTypedArrayClone(&Values); pJoined = pCopy != NULL ? xrtTypedArrayConcat(&Values, pCopy) : NULL; if ( (pCopy == NULL) || (pJoined == NULL) || !xrtTypedArrayEquals(&Values, pCopy) ) { xrtTypedArrayDestroy(pJoined); xrtTypedArrayDestroy(pCopy); xrtTypedArrayUnit(&Values); return 3; } printf("count=%zu joined=%zu index=%zu\n", xrtTypedArrayCount(&Values), xrtTypedArrayCount(pJoined), xrtTypedArrayFind(&Values, &iNeedle)); xrtTypedArrayDestroy(pJoined); xrtTypedArrayDestroy(pCopy); xrtTypedArrayUnit(&Values); return 0; }
Что только что произошло. (1) xrtTypedArrayInit(&Values, xrtTypeInt64()) — ведомо каноническим описанием встроенного int64: последующие Push копируют int64 (решает Ops описания), Equals сравнивает по int64 — в коде контейнера ноль специфичной для int64 логики. (2) После трёх Push Clone глубоко копирует, Concat склеивает (Values+Copy = 6 элементов), Equals(Values, Copy) решает равенство — глаголы владения работают по общему контракту (Clone глубоко, без разделения). (3) Печать count=3, joined=6, index=найденная позиция — массив типов времени исполнения ведёт себя изоморфно C-массиву; отличие только в том, что «тип приходит из описания». Подставьте xrtTypeString() или пользовательский тип — тот же набор API (всё ведомо описанием).
Вторая полная программа: словарь полей объекта
Вторая программа из examples/runtime/typed_dict — конкретизация динамических полей:
#include <stdio.h> #include <xruntime.h> /* 展示类型字典的复制键、默认值和插入顺序迭代。 */ int main(void) { xtypeddict Scores; xtypeddictiter Iterator; xstrview Key; int64 iAlice = 95; int64 iBob = 88; int64* pScore; if ( !xrtTypedDictInit(&Scores, xrtTypeInt64()) || !xrtTypedDictSet(&Scores, XRT_STR_LITERAL("alice"), &iAlice) || !xrtTypedDictSet(&Scores, XRT_STR_LITERAL("bob"), &iBob) ) { return 1; } pScore = (int64*)xrtTypedDictGetOrAdd( &Scores, XRT_STR_LITERAL("carol"), NULL ); if ( pScore == NULL ) { xrtTypedDictUnit(&Scores); return 2; } *pScore = 91; if ( !xrtTypedDictIterBegin(&Scores, &Iterator) ) { xrtTypedDictUnit(&Scores); return 3; } while ( (pScore = (int64*)xrtTypedDictIterNext( &Iterator, &Key )) != NULL ) { printf("%.*s=%lld\n", (int)Key.Size, Key.Data, (long long)*pScore); } xrtTypedDictIterEnd(&Iterator); xrtTypedDictUnit(&Scores); return 0; }
Что только что произошло. (1) Словарь с текстовыми ключами и ведомым описанием типом значения — Set/Take/Remove по общему контракту; ключи строки (встроенное описание STRING), значения произвольные (здесь демонстрационные типы). (2) Эта форма и есть нагрузка xrtdynamicfields главы 115 — таблица динамических полей объекта хозяина = словарный узел в графе объектов: граф трассирует словарный узел, а значения словаря трассируют ссылки объектов времени исполнения через xrtTypeValue() — три главы (114/113/116) сходятся здесь. (3) Семья компаньонов покрывает все шесть семейств (typed_stack/list/tree/set) плюс три формы очередей (typed_queue_spsc/mpsc/mpmc — typed-версия специализированных очередей топологии главы 21) и typed_value_containers (контейнеры с упаковкой Value) — по одному образцу на структуру — приёмочная грань контракта шести семейств.
117.4契约
- Заимствование описаний: шесть семейств заимствуют неизменяемый xrttype; стабильно на время регистрации и жизни контейнера (дисциплина главы 114).
- Равенство указателей: межконтейнерные операции требуют одинакового указателя описаний — одинакового ID/имени/размера/таблицы операций мало; встроенные и типы хозяина — процессно-канонические описания.
- Диспетчеризация операций над значениями: всё через семейство операций описания (ведомость таблицей) — контейнеры с нулём типоспецифичного кода.
- Глаголы владения: семейство Set копирует / SetTake атомарно переносит / семейство Take перемещает / семейство Remove по одному Drop / семейство Clone глубоко копирует.
- Легальность адресов: адреса значений — только внешние или точные живые слоты этого контейнера; структура/метаданные/ёмкость/паддинг/середина слота отвергаются; выход и источник перемещения вне контейнера.
- Атомарность отказа: видимое состояние неизменно при отказе; многошаговые сперва в рабочее хранилище, потом фиксация; OOM всегда XERR_MEMORY с сохранённой причиной.
- Переиспользование реализаций: внутренние быстрые пути контейнеров XRT напрямую — ноль налога производительности от typed-оболочки, один код алгоритмов.
- Граница обычных C-контейнеров: C-контейнеры без счёта ссылок и идентичности объекта — слабые ссылки/возврат циклов компонуются через runtime_object с контейнером в роли нагрузки.
- Особенности семейств: array непрерывный / stack LIFO / list разреженно-упорядоченный / tree сравнительно-упорядоченный / set уникальный / dict текстовые ключи — каждый специализирован поверх общего контракта.
Типичные ошибки
Симптом: Concat/сравнение двух контейнеров с «совершенно одинаковыми» типами элементов отвергается — каждый модуль статически объявил тип с одним именем.
Причина: равенство указателей — жёсткое правило; одинаковое содержимое не доказывает взаимозаменяемости жизненного ABI и частного контекста. Единообразно используйте процессно-канонические описания (встроенный xrtTypeInt64() / процессно-канонические описания реестра).
/* описания Counter, статически объявленные в модулях A и B независимо */ xrtTypedArrayConcat(&A_Array, &B_Array); /* указатели описаний разные: отвергнуто */
/* один источник описаний на весь процесс: брать процессно-каноническое из реестра */ const xrttype* T = host_registry_find("app.Counter"); xrtTypedArrayInit(&A, T); xrtTypedArrayInit(&B, T); xrtTypedArrayConcat(&A, &B); /* один указатель: взаимодействие возможно */
Симптом: клонировали контейнер, изменили один — другой следом; в значениях спрятаны ссылки/дескрипторные элементы.
Причина: Clone — глубокая копия, но «глубина» определяется Ops типа элементов: копия ссылочного элемента (значения-объекта) — это наращивание сильной ссылки (Copy=Ref у ObjectValueOps главы 115) — сам объект остаётся разделяемым (ровно как и должно быть у типов с ссылочной семантикой). Ожидание «разделить даже объекты» требует элементного собственного Clone.
pCopy = xrtTypedArrayClone(&Objs); /* клон массива объектов: ссылки +1, не новые объекты */ mutate(pCopy); /* изменилось тело: «изменился» и исходный контейнер */
/* ссылочная семантика - это свойство: много контейнеров делят один объект (возврат - забота графа) */ /* для действительно независимых копий: тип элемента даёт Ops глубокого Clone */
Симптом: ссылки объектов, удерживаемые контейнером, граф не трассирует — цикл течёт.
Причина: обычные typed-контейнеры не несут счёта ссылок и идентичности объекта — граф трассирует runtime_object (глава 115). Правильные пути: контейнер как нагрузка объекта (контейнер сам — объект) или узлы динамических полей (нагруза dict трассируется через операции значения типа).
/* голый typed array держит ссылки объектов в надежде, что граф сам обнаружит */ xrtTypedArrayPush(&Arr, &ObjRef); /* граф не знает о существовании Arr */
или использовать xrtdynamicfields (узлы-словари полей трассируют нативно) */Практические задания
Прогнать каждый из шести контейнеров через минимальный цикл (Init → две операции → Destroy) — с одним типом элемента (int64). Критерии приёмки: глаголы владения согласованы между семействами; атомарность отказа (имитация OOM) оставляет видимое состояние каждого семейства неизменным.
list с типом элемента xrtTypeValue() (упаковка динамических значений главы 31) — хранить int/string/object три вида значений и обходить с печатью (диспетчеризация по Kind). Критерии приёмки: три вида сосуществуют; глубокие клоны независимы (элементы со семантикой значений); сортировка по сравнению Value.
Таблица полей в стиле xrtdynamicfields: объект (глава 115) + нагруза typed dict + set/get/перечисление полей + трассировка графа объектов (сбор циклов у самоссылающихся полей). Критерии приёмки: сильные ссылки полей перечисляются Trace; самоссылочные циклы возвращаются в безопасной точке; типы полей вперемешку (int/string/объект).
Итоги
| Тема | Кратко |
|---|---|
| Позиционирование | шесть структур данных делят один контракт значений времени исполнения — ведомость типами, переиспользование быстрых путей XRT |
| Дисциплина описаний | заимствуют неизменяемые описания; межконтейнерные операции — равенство указателей (одинакового содержания мало) |
| Диспетчеризация значений | всё через семейство операций описания — ноль типоспецифичного кода контейнеров |
| Владение | Set копирует / SetTake атомарный перенос / Take перемещает / Remove по одному Drop / Clone глубоко |
| Атомарность отказа | видимое состояние неизменно; сперва рабочее хранилище, потом фиксация; OOM всегда MEMORY с причиной |
| Шесть семейств | array непрерывный / stack LIFO / list разреженный / tree сравнительный / set уникальный / dict текстовые ключи |
| Против xvalue | Value = одно динамическое значение (гетерогенные деревья); typed = однородные наборы (ноль метки на элемент) |
| Идентичность объекта | C-контейнеры без счёта ссылок — объектная семантика компонуется через runtime_object |
| Замыкание трассировки | динамические поля = нагруза dict + трассировка Value — сходятся 114/113/116 |
| Три формы очередей | spsc/mpsc/mpmc в typed-версии — продолжение выбора топологии главы 21 |
📖 Справочники API в этой главе:xruntime-typed_array (31 записей)xruntime-typed_dict (30 записей)xruntime-runtime_type (60 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки