Skip to content

RFC-029f: Роли целей компиляции и семантика поверхности импорта ​

Аннотация ​

Реализация расширенного слота «Плана дочерних RFC» RFC-029 (слоты 029b–029e заняты, далее следует 029f): определяет для исходных файлов модель ролей целей компиляции (пять категорий: Script / Bin / Lib / Test / Internal) и семантику их поверхностей импорта — при отсутствии манифеста роль выводится по достижимости из точки входа (нулевая конфигурация), при наличии манифеста объявляется явно через поля [lib] / [[bin]] / [exports] (поля, уже определённые в RFC-015). Заполняет четыре семантических пробела: по каким правилам считается поверхность межпакетного импорта, область применения исключения pub для предупреждений о мёртвом коде (решение B по #321), связь между [exports] и достижимостью в Registry, а также устранение терминологической неоднозначности между [binaries] в RFC-014b (предкомпилированные дистрибутивные артефакты) и файлами-источниками роли bin.

Мотивация ​

Основания и границы от ведущего документа ​

RFC-029 (принят) с решениями по выбору точки входа и видимости является непосредственным предшественником настоящего RFC:

Выбор файла входа, приоритет: 1. [run].main 2. поле path первого [[bin]] 3. src/main.yx (соглашение по умолчанию). — RFC-029 §Межпакетные циклы: пояснения

Видимость: отсутствует. Область видимости + граница дистрибуции покрывают все сценарии, новое ключевое слово не требуется. — RFC-029 §Журнал проектных решений (2026-07-30)

Второй пункт — проектная красная линия настоящего RFC: семантика поверхности импорта не должна вводить новое ключевое слово видимости, её может нести только «граница дистрибуции» (роль файла + объявленная поверхность экспорта).

Каждый из четырёх потребителей упёрся в одну и ту же стену:

ПотребительТочка столкновения
#321 предупреждения о мёртвом кодеРешение B «pub = всегда интерфейс наружу, без предупреждений» принято из-за отсутствия модели ролей, различающей «настоящий наружный» и «похожий на наружный»; вопрос «предупреждать ли о неиспользуемом pub внутри bin» (вариант A) повис в воздухе
RFC-014c Рабочие пространства (на рассмотрении, #113)При взаимном use пакетов-членов по чему считается поверхность импорта — [exports], один файл [lib], или все pub-файлы — ни одного семантического положения во всём тексте
RFC-015 Система конфигурации (принят)Поля [lib] / [[bin]] / [exports] определены; кроме приоритета точки входа, потреблённого RFC-029, семантика остальных полей (особенно связь [exports] с достижимостью в Registry) никем не согласована
RFC-037 Упаковка / RFC-029a Кэширование (проект, #293)Выбор артефактов упаковки и граница единиц кэша для инкрементальной перекомпиляции требуют модели ролей как предпосылки

Текущие проблемы ​

Четыре семантических пробела (инвентаризация 2026-09-12, сверено по полным текстам 014/014a/014b/014c/015/029):

  1. Межпакетная поверхность импорта не определена. Пример взаимного импорта пакетов-членов в 014c содержит только структуру каталогов (src/lib.yx), и нигде не сказано, что можно получить через use utils.helper. Текущее неявное правило — «все файлы верхнего уровня с pub в этом пакете» — внутренние файлы реализации также затягиваются в пространство имён, и граница зависимостей перестаёт существовать.
  2. Исключение pub для мёртвого кода не имеет области применения. Решение B по #321 «pub никогда не предупреждает» слишком консервативно в однофайловом скрипте (у скрипта нет внешних потребителей, pub бессмысленен) и слишком либерально в файле входа многопроектного проекта (никто не импортирует pub корневого файла — это мёртвый код). Без модели ролей линия раздела между вариантами A и B не проводится.
  3. Три концепции экспорта не согласованы. [exports] (015, отображение путей), [lib] (015, путь к одному файлу), достижимость в Registry (029, от входа по use) — соотносятся как включение, равенство или независимы — нигде не зафиксировано.
  4. Столкновение терминов. [binaries] в RFC-014b — это предкомпилированные дистрибутивные артефакты (стратегия приоритета загрузки .so/exe), что совершенно иной уровень, чем «цель bin (роль в исходниках)». После приземления 014c оба неизбежно окажутся на одном экране, и без скорейшего устранения неоднозначности недоразумения будут множиться.

Эмпирическое подтверждение: консервативность решения B — уже реальная боль. Запись ретроспективы M2 в #321: структурная причина молчания всей группы W1001–W1005 в конфигурации по умолчанию — именно «весь pub считается наружным интерфейсом» — это компенсирующее поведение из-за отсутствия модели, а не финальное состояние.

Предложение ​

Основная конструкция: модель ролей файлов ​

Роль — атрибут уровня файла (согласуется с гранулярностью полей в 015: [lib] — один файл, [[bin]] — список файлов, [exports] — отображение файлов), а не переключатель уровня пакета.

РольОпределениеСемантика
BinФайл, на который указывают [run].main / [[bin]].path; или (без манифеста) файл, содержащий main и не используемый (use) ни одним другим файломТочка входа программы. Требует main и эта функция должна быть функцией; иначе ошибка компиляции (E3020 — отсутствует main / E3021 — main не функция), причём на верхнем уровне не должно быть исполняемых операторов. Её pub может порождать предупреждения о мёртвом коде (нет внешних потребителей); main — корень достижимости
LibФайл, попавший в отображение [exports]; или [lib].path; или (без манифеста) файл, на который ссылается use другого файлаГраница дистрибуции. Её pub исключён из предупреждений о мёртвом коде (внешние потребители пакета невидимы, лучше недосообщить); экспортируемые через неё символы видны между пакетами
TestФайл, удовлетворяющий правилам тестовых файлов RFC-036 (каталог tests/, *_test.yx и пр. существующие соглашения); явное объявление [[test]] не вводитсяТестовый код. Не участвует в анализе мёртвого кода; его ссылки считаются корнями достижимости для тестируемого кода
InternalВ пакете с манифестом — файл, не входящий в поверхность экспорта и не содержащий mainВнутренняя реализация пакета. pub исключён до второго этапа (ужесточение по достижимости в графе use внутри пакета, при предварительной верификации корпусом роли Bin); use извне пакета недостижим
ScriptФайл, запускаемый напрямую как одиночный (run foo.yx)Понятия точки входа нет. Операторы верхнего уровня (включая инициализацию связываний) выполняются в порядке написания и составляют тело программы; main — обычное связывание, не вызывается автоматически — чтобы запустить, напишите main(). В Registry присутствует только std

Приоритет определения: явное объявление в манифесте > вывод по достижимости из входа > соглашения 036 для тестов. Явное объявление всегда побеждает — вывод лишь умолчание при отсутствии объявления.

Определение точки входа ​

Модель ролей используется не только для анализа мёртвого кода, но и управляет определением точки входа. Правила входа для ролей:

РольПравило входа
Script (без манифеста)Понятия точки входа нет. Операторы верхнего уровня (включая инициализацию связываний) выполняются в порядке написания и составляют тело программы; main — обычное связывание, не вызывается автоматически — чтобы запустить, напишите main()
Bin (с манифестом)Требует main и эта функция должна быть функцией; иначе ошибка компиляции (E3020 — отсутствует main / E3021 — main не функция). На верхнем уровне не должно быть исполняемых операторов
Lib / Internal / TestНе требуют main (они не являются точкой входа)

Script и Bin должны быть взаимоисключающими по точке входа: «в Script main не особенный» и «в Bin main — это вход» не могут сосуществовать — если бы Script одновременно выполнял операторы верхнего уровня и неявно вызывал main, скрипт с явным main() запускался бы дважды (#356). Поэтому в Script лишь одна точка исполнения: операторы верхнего уровня.

Различие между определением роли и определением точки входа (замечание о реализации): определение точки входа использует «наличие манифеста» (find_project_root().is_some()), а не roles::classify(). Причина: classify отвечает на вопрос «кто потребляет этот файл» (для анализа мёртвого кода); файл с манифестом, но без main, в classify получает роль Internal (его никто не use-ит, и это разумно), но пользователь запускает его как вход — значит, вход должен быть. Два вопроса различны — различны и критерии.

См. docs/src/reference/language-spec/syntax.md §3.11.

Примеры ​

toml
# yaoxiang.toml (все поля уже определены в RFC-015, настоящий RFC не вводит новых конфигурационных поверхностей)
[lib]
path = "src/lib.yx"

[[bin]]
name = "my-cli"
path = "src/cli.yx"

[exports]
"." = "src/lib.yx"
"./internal-helper" = "src/helper.yx"   # ← межпакетную видимость определяет именно оно, а не pub
yaoxiang
# src/cli.yx (роль Bin)
pub unused_fn = (x: Int) => x    # ← допускается W1001: у bin нет внешних потребителей
main: () -> Void = { ... }

# src/lib.yx (роль Lib, на поверхности экспорта)
pub api_fn = ...                 # ← предупреждение не выдаётся: внешние потребители пакета невидимы

# src/other.yx (роль Internal, вне поверхности экспорта)
pub semi_api = ...               # ← исключён до второго этапа (лучше недосообщить), затем ужесточение по графу use

Семантика поверхности импорта ​

Порядок разрешения межпакетного use pkg.x (первое совпадение вступает в силу):

  1. Если есть отображение [exports] → поверхность импорта = набор файлов, перечисленных в отображении; x должен быть среди связываний верхнего уровня этих файлов;
  2. Иначе, если есть [lib].path → поверхность импорта = этот единственный файл;
  3. Если ничего нет (нет манифеста зависимости, голая зависимость по пути) → сохраняется текущее поведение: импортируемы все файлы верхнего уровня (совместимость с существующим поведением; ужесточение — отдельный вопрос).

Члены рабочего пространства: поверхность экспорта определяется только манифестом самого пакета-члена; корень рабочего пространства не может перекрывать или расширять поверхность экспорта членов — самодостаточность членов — ключевая конструкция 014c, и перекрытие на уровне корня разрушило бы инкапсуляцию.

Связь с достижимостью в Registry (согласовано с 029): [exports]/[lib] определяют множество допустимых начальных точек для межпакетного разрешения; после определения начальных точек Registry по-прежнему расширяется по правилу 029 «достижимое из входа по use» — внутренние файлы, которые use-ит экспортируемый файл, попадают при линковке, но не порождают новых межпакетных начальных точек.

Границы с существующими планами ​

СоседГраница
RFC-029d «CLI --entry перекрытие входа» (планируется)029f определяет модель ролей; 029d её потребляет — --entry является средством CLI-уровня для перекрытия роли Bin
RFC-014b [binaries]Устранение неоднозначности терминов: [binaries] — это предкомпилированные дистрибутивные артефакты (стратегия приоритета загрузки), [[bin]] — это объявление роли в исходниках. Первые присутствуют в манифесте зависимого пакета, вторые — в манифесте текущего пакета; не взаимозаменяемы
RFC-014c Рабочее пространствоПоверхность импорта при взаимном цитировании пакетов-членов = порядок из настоящего RFC; механизмы координации рабочего пространства (members, общий lockfile) остаются в 014c
RFC-036 ТестыОпределение роли Test напрямую использует существующие файловые правила 036, новых соглашений не вводится
#321 Мёртвый кодРешение B (pub всегда исключён) явно понижается до «переходной семантики при отсутствии target»; возможность предупреждения pub в роли Bin — форма реализации варианта A в настоящем RFC

Детальная конструкция ​

Алгоритм определения роли (внутри оркестратора) ​

fn classify(files, manifest, entry_reach) -> Map<File, Role>:
    # 1. Уровень явных объявлений: манифест имеет приоритет
    for f in manifest.exports.values():  role[f] = Lib
    if manifest.lib_path:                role[lib_path] = Lib
    for b in manifest.binaries:          role[b.path] = Bin
    if manifest.run_main:                role[run_main] = Bin

    # 2. Уровень вывода: при отсутствии объявления — по достижимости из входа
    for f in files where role[f] не определена:
        if f используется (use) ≥ 1 другим файлом:   role[f] = Lib
        elif f содержит main и никто не use-ит его:   role[f] = Bin
        else:                                         role[f] = Internal

    # 3. Уровень тестов: срабатывание правил 036 → Test (перекрывает Lib/Internal, не перекрывает явный Bin)
    apply_rfc036_test_rules(files, &role)

    # Одиночный файл без манифеста: модель обходится, поведение не меняется
    if no manifest and single_file:      all Script

Изменения в компиляторе ​

  • frontend/config.rs: парсинг манифеста дополняется отображением [exports] → таблица ролей (разбор полей уже есть в 015).
  • frontend/module/orchestrator.rs: перед построением Registry вставляется фаза классификации ролей; межпакетное разрешение проверяет цели use по порядку поверхности импорта, при выходе за границу выдаётся существующее семейство module_not_found (новые коды ошибок не вводятся, в сообщение добавляется подсказка «не входит в поверхность экспорта»).
  • typecheck/passes/dead_code.rs: множество корней входа меняется с «main + весь pub» на «main + pub файлов роли Lib» (Bin — немедленно с предупреждением; Internal — исключение до второго этапа, Phase 2 см. в стратегии реализации).
  • LSP (RFC-017): автодополнение/наведение фильтруют «импортируемые элементы» по поверхности импорта; диагностика отсутствия main выдаётся только для файлов роли Bin.

Поведение во время выполнения ​

Без изменений. Модель ролей — это чисто семантика времени компиляции и статического анализа, IR и исполнение не затрагиваются.

Обратная совместимость ​

  • Проекты без манифеста: поведение полностью не меняется (уровень вывода воспроизводит текущее состояние — используемый — Lib, содержащий main — Bin). Приращение предупреждений о мёртвом коде только в Bin-файлах: неиспользуемый pub корневого файла, который никто не импортирует, из молчания переходит в W1001 — это и есть ожидаемое поведение варианта A из #321, а не поломка.
  • Проекты с манифестом: если [exports] объявлен, поверхность импорта сужается с «все pub-файлы» до объявленной. Это единственная точка сужения поведения: код, зависящий от внутренних файлов чужих пакетов, начнёт получать module_not_found. Учитывая, что экосистема пакетов ещё не сложилась (029: «в настоящее время экосистемы сторонних пакетов нет»), площадь поломки равна нулю; всё же закладывается переходный период в дополнительной версии (сначала предупреждение, затем ошибка).

Компромиссы ​

Достоинства ​

  • Нулевая новая конфигурационная поверхность: все поля уже определены в 015, настоящий RFC лишь дополняет семантику.
  • Нулевое новое ключевое слово: поверхность импорта несёт граница дистрибуции — изоморфно решению 029 «видимости не существует».
  • Четыре потребителя (#321 / 014c / 029a / 037) моделируются разом, никто больше не упирается в свою стену.
  • Путь без манифеста — нулевая стоимость: ДНК скриптового языка с нулевой конфигурацией сохранена полностью.

Недостатки ​

  • Вывод роли (используемый — Lib) в распространённой форме «файл входа use-ит утилитарный файл» приписывает утилитарному файлу роль Lib, и его pub исключается — грубее идеальной гранулярности. Это осознанный выбор в сторону «лучше недосообщить», ужесточение требует анализа направления вызовов в графе use и в первой версии не выполняется.
  • Сужение поверхности импорта через [exports] — это семантическое ужесточение с эффектом поломки (несмотря на переходный период).

Альтернативные варианты ​

ВариантОписаниеПричина отклонения
Открыть на верхнем уровне RFC-040Новый номер верхнего уровняСемантика модульного графа (поверхность импорта / достижимость / вход) целиком лежит во владении 029; выделение породит две верхнеуровневые записи об одном предмете; к тому же шаблон слотов 029b–029e уже установлен
Вложить в 014cКак раздел рабочего пространстваНаправление зависимости обратное: языковой уровень (029x) определяет семантику, уровень пакетов (014x) её потребляет. 014c ещё на рассмотрении, расширение в процессе замедлит его приземление
Вариант A в исходном виде (меню #321)манифест форсирует target [[bin]]/[[lib]] для получения семантики мёртвого кодаРади узкого среза (неиспользуемый pub корневого файла с main) вводится обязательная конфигурация; уровень вывода в настоящем RFC даёт тот же выигрыш при нулевой конфигурации
Только вывод по входу (без уровня манифеста)Не потребляются поля 015[exports] — уже принятое поле 015, и межпакетная поверхность импорта не обходится без него; отсутствие определения означает пробел для 014c

Стратегия реализации ​

Поэтапность ​

  • Phase 1: классификация ролей + разрешение поверхности импорта + возможность предупреждения pub в Bin (Internal/Test сохраняют исключение)
  • Phase 2: ужесточение pub в Internal до «предупреждать, если недостижимо в графе use внутри пакета» — предпосылкой служит верификация на корпусе Bin-роли после приземления Phase 1, подтверждающая отсутствие ложных срабатываний; выполняется по факту готовности, без расписания

Зависимости ​

  • Предпосылки: оркестратор RFC-029 (приземлён), поля манифеста RFC-015 (определены).
  • Зависимые: #321 (предупреждение pub в Bin), RFC-014c (поверхность импорта), RFC-029a (роль как граница единиц кэша), RFC-037 (выбор артефактов), RFC-029d (перекрытие через --entry).
  • Порядок приземления с RFC-014c: до завершения рассмотрения 014c порядок разрешения импорта из 029f должен быть зафиксирован, 014c потребляет его по ссылке, чтобы избежать расширения области рассмотрения в процессе.

Риски ​

  • Расхождение между выводом роли и интуицией пользователя (первый пункт компромиссов) → всё приращение поведения выдаётся в форме предупреждений (без блокировки), в любой момент можно отключить.
  • Управление переходным периодом сужения [exports] → в дополнительной версии сначала предупреждение.

Открытые вопросы ​

Все три вопроса этапа проекта решены (2026-09-13, решения внесены в Приложение B и в основной текст), нерешённых нет:

  • [x] Ужесточать ли pub в Internal → Решение: на втором этапе ужесточение по достижимости в графе use внутри пакета, при предварительной верификации корпусом Bin-роли (см. Phase 2 стратегии реализации)
  • [x] Вводить ли явное объявление [[test]] → Решение: не вводится, роль Test определяется только правилами 036; при появлении у 036 потребности в явном объявлении оно будет добавлено в самом 036
  • [x] Допустимо ли перекрытие поверхности экспорта членов со стороны корня рабочего пространства → Решение: не допускается, самодостаточность членов — ключевая конструкция 014c, и перекрытие на уровне корня разрушило бы инкапсуляцию

Приложение B: Журнал проектных решений ​

РешениеСодержаниеДатаЗаписалОснование
Номер029f (029b зарезервирован под hot reload, 029c удалён, 029d/029e заняты)2026-09-12ЧэньсюйRFC-029 §План дочерних RFC
Отнесение к семейству 029, а не к верхнему RFCДа2026-09-12ЧэньсюйПоверхность импорта / достижимость / вход относятся к семантике модульного графа; 029 уже потребляет приоритет входа из [[bin]]
Ключевое слово видимостиНе вводится2026-09-12ЧэньсюйИзоморфно решению RFC-029 «видимости не существует», поверхность импорта несёт граница дистрибуции
Статус решения B по #321Понижается до «переходной семантики при отсутствии target»2026-09-12ЧэньсюйВозможность предупреждения pub в роли Bin — форма реализации варианта A из #321
Гранулярность ролиУровень файла2026-09-12ЧэньсюйГранулярность полей 015 ([lib] — один файл, [[bin]] — список, [exports] — отображение)
Устранение неоднозначности [binaries]предкомпилированные дистрибутивные артефакты из 014b ≠ роль источников [[bin]]2026-09-12ЧэньсюйДва уровня понятий (стратегия загрузки vs роль компиляции), фиксируется до приземления 014c
Ужесточение pub в InternalНа втором этапе по достижимости в графе use внутри пакета, при предварительной верификации корпусом Bin-роли2026-09-13ЧэньсюйЛучше недосообщить, постепенное ужесточение, корпусо-управляемый подход избегает преждевременных ложных срабатываний
Явное объявление [[test]]Не вводится; Test определяется только правилами 0362026-09-13ЧэньсюйДо появления потребности у 036 конфигурационная поверхность не добавляется
Перекрытие корнем рабочего пространства поверхности экспорта членовНе допускается2026-09-13ЧэньсюйСамодостаточность членов — ключевая конструкция 014c, перекрытие на уровне корня разрушило бы инкапсуляцию
Автовызов main в ScriptНе автовызов — операторы верхнего уровня и есть программа, main() пишется явно2026-09-17ЧэньсюйСосуществование двух правил привело бы к двойному запуску при явном main() (#356); у Script только одна точка исполнения
Использовать ли модель ролей для определения точки входаДа (ранее только для предупреждений о мёртвом коде)2026-09-17ЧэньсюйНастоящий RFC определяет в Bin «main — корень достижимости», без подключения входа эта семантика повисает в воздухе

Приложение C: Глоссарий ​

ТерминОпределение
Роль (Role)Идентификатор цели компиляции исходного файла: Script / Bin / Lib / Test / Internal
Поверхность импорта (Import Surface)Множество файлов — допустимые начальные точки для межпакетного use, определяется объявлениями [exports]/[lib] или выводом
Граница дистрибуцииТермин 029: область внешней экспозиции пакета; настоящий RFC уточняет её с неявной (все pub-файлы) до поверхности импорта
Состояние ScriptОбходная форма для одиночного файла без манифеста, запускаемого напрямую; всё поведение совпадает с текущим

Ссылки ​

  • RFC-029 Семантика модулей (родительский RFC: выбор входа, решения по видимости, план дочерних RFC)
  • RFC-015 Система конфигурации (определения полей [lib]/[[bin]]/[exports])
  • RFC-014b Система сборки и дистрибуция бинарников ([binaries], объект устранения неоднозначности терминов)
  • RFC-014c Поддержка рабочих пространств (первый потребитель поверхности импорта, на рассмотрении)
  • RFC-036 Тестовый фреймворк (источник правил для роли Test)
  • #321 M2 Отдельный канал эмиссии кодов предупреждений (источник решения B и варианта A)