RFC-026:Основные механизмы FFI
Справка:
Устарело:
- RFC-020: Динамические модули и интеграция FFI — содержимое объединено в этот документ
- RFC-021: Расширение FFI на основе библиотек и поддержка межъязыковых вызовов — содержимое объединено в этот документ
Подчинённые RFC:
- RFC-026a: Расширяемая система механизмов FFI — плагины для нескольких ABI, абстракция
FfiMechanism, динамическая загрузка- RFC-026b: Инструментарий yx-bindgen — генерация привязок
.yxиз заголовочных файлов C
Аннотация
В данном документе определяются основные механизмы FFI (Foreign Function Interface) языка YaoXiang. Ключевая идея: внешние библиотеки являются значениями первого класса на этапе компиляции, распределение памяти для данных, передаваемых через границу, фиксируется при определении типа, а объекты кучи YaoXiang структурно изолированы от внешнего кода.
- Внешние библиотеки как значения:
Native.c("libsqlite3")выполняет компоновку библиотеки на этапе компиляции и возвращает парсер, использующий каррирование для хранения информации о библиотеке - Внешние символы как значения:Применение парсера к имени символа даёт внешнюю ссылку, которая связывается с типом или функцией через
name: type = value(RFC-007/010) - Дихотомия типов:Непрозрачные дескрипторы (распределение памяти принадлежит внешней стороне) / прозрачные типы (распределение памяти принадлежит YaoXiang), третьего не дано
- Изоляция маршалинга:Данные, передаваемые через границу, по умолчанию копируются во временную зону вызова, объекты кучи YaoXiang изолированы от внешнего кода
- Безопасность владения:Единоличное владение дескриптором (Move) + RAII, структурно исключает двойное освобождение и use-after-free
- Люк безопасности:
*Tсырой указатель +unsafe {}, пользователь явно принимает риск прямого доступа к памяти без копирования
Основные границы — пять неизменяемых контрактов:
1. Компоновка библиотеки на этапе компиляции, верификация символов на этапе компиляции
2. Принадлежность распределения памяти типа определяется при определении: непрозрачные дескрипторы принадлежат внешней стороне, прозрачные типы принадлежат YaoXiang
3. Маршалинг по умолчанию через копирование во временную зону, объекты кучи YaoXiang изолированы от внешнего кода
4. Единоличное владение дескриптором + Move, структурная защита от двойного освобождения/висячих ссылок
5. Внешний код всегда работает с памятью с определённым распределением и определённым владением, никаких неоднозначностейМотивация
Текущая ситуация и цели
Текущий native("symbol") в кодовой базе — это лишь механизм диспетчеризации для вызова функций std языка Rust из байткода YaoXiang (FfiRegistry = HashMap<String, RustFnPtr>), без какой-либо реальной границы ABI — без dlopen, без маршалинга по C ABI, без передачи владения памятью через границы.
Настоящий FFI должен решать четыре проблемы:
| Проблема | Ответ данного RFC |
|---|---|
| Разрешение символов | Библиотека как значение первого класса с компоновкой на этапе компиляции (Native.c("lib")), верификация символов на этапе компиляции |
| Маршалинг значений | Управляется сигнатурой, компилятор определяет правила преобразования для каждой позиции параметра на этапе компиляции |
| Владение памятью | Дихотомия типов определяет принадлежность; копирование по умолчанию для изоляции |
| Безопасность времени жизни | Move + RAII + ограничение заимствования для одного вызова |
RFC-020 и RFC-021 определяли различные аспекты FFI с перекрытием, данный документ объединяет их в единую спецификацию.
Цели проектирования
- Ноль утечек сырых указателей в пользовательском коде:В обычном FFI-использовании, в исходном коде
.yxне появляются сырые указатели - Явная принадлежность распределения:Пользователь определяет тип и решает, кому принадлежит эта память, без выводов во время выполнения
- Структурная безопасность:Без утечек, без двойного освобождения, без use-after-free — гарантируется системой типов, а не соглашениями
- Честные границы доверия:C не может предоставить типы контрактов, верифицируемые на этапе компиляции, доверие локализовано в объявлениях привязок
- Совместимость с самохостингом:Без чрезмерных абстракций, уникальных для языка-хоста
За пределами охвата
- Расширяемая система плагинов для нескольких ABI (Wasm/Python/пользовательские ABI):см. RFC-026a
- Инструментарий yx-bindgen:см. RFC-026b
- Экспорт функций YaoXiang для вызова из C (обратный FFI):последующий RFC, в данном документе только объявлены принципы
- Встроенный ассемблер, SIMD intrinsics:не входят в охват данного RFC
Предложение
1. Внешние библиотеки и символы: значения первого класса с каррированием
Пробел информации FFI — "какую библиотеку компоновать, какой символ" — заполняется превращением библиотеки в значение первого класса без введения новых ключевых слов.
1.1 Библиотека как значение
// Native.c применяет имя библиотеки → компонует библиотеку на этапе компиляции, возвращает парсер символов
sqlite3 = Native.c("libsqlite3")Native.c("libsqlite3") — это действие на этапе компиляции + значение на этапе выполнения:
- Этап компиляции:Компоновщик
-lsqlite3, библиотека входит в таблицу символов, существование символа верифицируемо - Значение:
sqlite3— это парсер, применение имени символа даёт внешнюю ссылку на эту библиотеку
.c — метка механизма ABI (C ABI). В ядре встроена только .c; другие механизмы (.wasm и др.) см. RFC-026a.
1.2 Символ как значение, связывание как name: type = value
Применение парсера к имени символа даёт внешнюю ссылку, которая связывается через унифицированный синтаксис RFC-007/010. Типовая аннотация LHS определяет, является ли эта ссылка типом или функцией:
sqlite3 = Native.c("libsqlite3")
// LHS — это Type → связывается как непрозрачный тип
SqliteDb: Type = sqlite3("sqlite3")
// LHS — это сигнатура функции → связывается как функция
SqliteDb.open: (file: String) -> ?SqliteDb = sqlite3("sqlite3_open")
SqliteDb.exec: (sql: String) -> Int32 = sqlite3("sqlite3_exec")
SqliteDb.close: () -> Int32 = sqlite3("sqlite3_close")
// .drop — обычное связывание метода (соглашение RAII из RFC-009)
SqliteDb.drop = SqliteDb.closeВерификация на этапе компиляции:sqlite3("sqlite3_open") — символ sqlite3_open должен присутствовать в таблице символов libsqlite3, иначе ошибка компиляции.
1.3 Связывание методов и позиция self
В записи Type.method: (...) -> ... self неявно на первой позиции — при вызове db.exec("SELECT"), db передаётся как нулевой параметр C-функции sqlite3_exec.
Если нужно связать уже объявленную независимую функцию как метод, используется синтаксис [N] для указания позиции self (каррирование с несколькими позициями из RFC-004):
// Независимая функция
sqlite3_close_v2: (db: SqliteDb) -> Int32 = sqlite3("sqlite3_close_v2")
// Связывается как метод, [0] означает, что db — это self
SqliteDb.soft_close = sqlite3_close_v2[0]Прямое связывание методов Native.c(...) и ручное связывание [N] — оба используют name: type = value, оба помещают значение функции справа от =, никаких двух механизмов.
1.4 Использование пользователем: ноль unsafe, ноль сырых указателей
import sqlite3_bindings
db = SqliteDb.open("test.db")
db.exec("SELECT * FROM users")
// ← конец области видимости, RAII автоматически вызывает SqliteDb.drop → sqlite3_close(db)2. Дихотомия типов: принадлежность распределения фиксируется при определении
Когда внешние данные попадают в YaoXiang, задаётся только один вопрос: кто определяет распределение этой памяти?
├─ Распределение — чёрный ящик внешней стороны (sqlite3, FILE*, дескриптор сокета)
│ → Непрозрачный дескриптор = lib("symbol")
│ → YaoXiang только хранит указатель, никогда не разыменовывает, передаёт только между функциями библиотеки
│ → Внешний код читает свою память, YaoXiang не трогает
│
└─ Распределение определено YaoXiang (timespec, point, struct с полями для чтения)
→ Прозрачный тип = { field: Type, ... }
→ YaoXiang владеет памятью, определяет распределение, читает/записывает поля
→ Внешний код читает/записывает в память с определённым YaoXiang распределениемТретьего не дано. Трёхуровневая модель памяти (копирование/接管/системный уровень) из предыдущих проектов — это паттерное мышление; истина — дихотомия принадлежности распределения.
2.1 Непрозрачные дескрипторы: распределение принадлежит внешней стороне
SqliteDb: Type = sqlite3("sqlite3")- Внутри YaoXiang хранится только дескриптор размером с указатель
- Пользователь не может конструировать (
SqliteDb {}→ ошибка компиляции), не может обращаться к полям (нет полей для доступа) - Единственный источник: внешняя функция, возвращающая
SqliteDb - При вызове метода дескриптор передаётся обратно в библиотеку, библиотека читает свою память (структура
sqlite3в куче библиотеки)
Внешний код "читает внутренности" — это чтение структуры, которую он сам выделил, YaoXiang просто переносит дескриптор. Без конфликтов памяти.
2.2 Прозрачные типы: распределение принадлежит YaoXiang
// Поля значимы, нужно читать/записывать → прозрачный тип, распределение объявлено YaoXiang
Timespec: Type = {
tv_sec: Int64,
tv_nsec: Int64
}
clock_gettime: (clk: Int32, ts: *Timespec) -> Int32 = Native.c("librt")("clock_gettime")
ts = clock_gettime(CLOCK_REALTIME) // см. §3, маршалинг через временную зону
print(ts.tv_sec) // YaoXiang читает согласно своему определению полейВнешний код читает/записывает в память с распределением, определённым YaoXiang, которой YaoXiang владеет. Распределение — это контракт YaoXiang, а не внешней стороны.
2.3 Правило принятия решения
Пользователю нужно определить только одно: нужно ли мне читать поля этого типа?
| Решение | Тип | Принадлежность распределения |
|---|---|---|
| Поля не читаю, передаю дескриптор между функциями библиотеки | Непрозрачный дескриптор = lib("sym") | Внешняя сторона |
| Нужно читать/записывать поля | Прозрачный тип { ... } | YaoXiang |
3. Маршалинг: управляется сигнатурой, изоляция через временную зону
Преобразование данных, передаваемых через границу, управляется сигнатурой, компилятор определяет правила преобразования для каждой позиции параметра. Основная гарантия безопасности: внешний код читает/записывает в маршальную временную зону, а не в объекты кучи YaoXiang.
3.1 По умолчанию через копирование во временную зону
YaoXiang → C (входные параметры):
Копирование данных во временную зону вызова → передача указателя на временную зону в C
→ C вышел за границы/испортил → повреждена только временная зона, объекты кучи YaoXiang изолированы
C → YaoXiang (возвращаемые/выходные параметры):
C пишет во временную зону → YaoXiang копирует обратно в свой объект через memcpy
→ C не касается финального объекта YaoXiangВнешний код всегда читает/записывает в маршальную временную зону, полностью изолированную от объектов кучи YaoXiang. Неправильное объявление распределения, C сохранил висячий указатель, C вышел за границы — всё это повреждает только временную зону, объект YaoXiang цел. Цена — одно memcpy.
3.2 Таблица правил маршалинга
Направление входных параметров (YaoXiang → C):
| Тип YaoXiang | C представление | Действие маршалинга | Владение |
|---|---|---|---|
Int32/Int64/Float | int/long/double | Помещается напрямую в регистр, без преобразования | Семантика значения |
String | const char* | Предоставляется временное представление только для чтения (временно, действительно во время вызова) | YaoXiang сохраняет, C только читает |
| Прозрачный тип | struct T* | Копирование во временную зону, передача указателя на временную зону | YaoXiang владеет объектом, C читает копию |
| Непрозрачный дескриптор | void* | Извлекается внутренний указатель дескриптора | YaoXiang владеет, предоставляется C |
*T | T* | Передаётся напрямую сырой указатель (unsafe) | Ответственность пользователя |
Направление возврата (C → YaoXiang):
| Возвращаемое C | Тип YaoXiang | Действие маршалинга | Владение |
|---|---|---|---|
int/double | Int32/Float | Чтение напрямую из регистра | Семантика значения |
char* | String | strlen + memcpy в String YaoXiang | YaoXiang владеет копией, исходная память не трогается |
struct T* (новый дескриптор) | Непрозрачный дескриптор | Дескриптор сохраняется в объекте YaoXiang | YaoXiang принимает владение |
struct T (значение/выходной параметр) | Прозрачный тип | C пишет во временную зону → memcpy в YaoXiang | YaoXiang владеет |
char* (статическая область) | *const U8 | Сохранение сырого указателя без копирования (unsafe чтение) | Не принимается владение, ответственность пользователя |
3.3 Жизненный цикл заимствования: строго ограничен одним вызовом
Указатели, которые YaoXiang предоставляет внешнему коду (временное представление String только для чтения, временная зона прозрачного типа, дескриптор), имеют жизненный цикл, строго ограниченный одним вызовом:
- Во время вызова: указатель действителен, внешний код может читать/записывать
- После возврата из вызова: заимствование немедленно прекращается
Если внешний код сохранил указатель для использования после вызова — это нарушение внешним кодом стандартного FFI-контракта (эквивалентно багу библиотеки), YaoXiang за это не отвечает. Это согласуется с контрактами FFI C во всех языках (заимствование &T в Rust при передаче в C также ограничено).
3.4 String никогда не отдаёт персистентный указатель
String — ключ к "C не вмешивается в память YaoXiang":
- В C: предоставляется временное представление только для чтения, действительное во время вызова
- Из C: strlen + memcpy в копию, которой владеет YaoXiang
C никогда не получает персистентный указатель на String YaoXiang, YaoXiang никогда не хранит долгосрочную ссылку на C char*. Структурная изоляция.
4. Владение и жизненный цикл: Move + RAII
Непрозрачные дескрипторы следуют модели владения из RFC-009, без новых концепций.
4.1 Основные принципы
- Семантика Move:Непрозрачные дескрипторы по умолчанию Move, присваивание/передача параметров/возврат = передача владения, нельзя копировать
- Единоличное владение дескриптором:В любой момент времени дескриптор имеет только одного владельца → структурно исключает двойное освобождение
- RAII-освобождение:В конце области видимости, если привязан
.drop, автоматически вызывается - Отслеживание потребления:После явного уничтожения или Move переменная потреблена, нельзя использовать снова → исключает use-after-free
4.2 .drop — это опциональный побочный эффект
SqliteDb.drop = SqliteDb.close // в конце области видимости вызывается sqlite3_close.drop — это не механизм предотвращения утечек YaoXiang — хранение дескриптора на стороне YaoXiang (значение размером с указатель) автоматически освобождается, не связано с .drop. .drop — это опциональный побочный эффект вызова внешней функции в конце области видимости:
- Привязан
.drop→ в конце области видимости вызывается (очистка внешних ресурсов) - Не привязан
.drop→ ничего не делается, не ошибка, не предупреждение
Нужно ли очищать внешние ресурсы — это вопрос спецификации внешней библиотеки (getenv возвращает статическую область, не надо освобождать; глобальный синглтон не надо освобождать), YaoXiang не берёт на себя полномочия强制. Защита от утечек через Move + единоличное владение (безусловно, структурно), а не через .drop.
4.3 Автоматическое уничтожение и порядок
{
db = SqliteDb.open("test.db")
stmt = db.prepare("SELECT * FROM users")
// ← конец области видимости, обратный порядок автоматического уничтожения (вызывается только если есть .drop):
// stmt.drop() → sqlite3_finalize(stmt)
// db.drop() → sqlite3_close(db)
}Порядок уничтожения: обратный порядку определения, как в RAII.
4.4 Move и потребление
db = SqliteDb.open("test.db")
db2 = db // Move: передача владения
db.exec("...") // ❌ Ошибка компиляции: db уже перемещён, после потребления нельзя читать
process_db: (db: SqliteDb) -> Void = {
db.exec("...")
// ← конец функции, db уничтожается здесь
}
process_db(some_db) // Move в функцию
// some_db здесь уже недействителен4.5 Обработка Null
// Может вернуть null → ?T, пользователь должен обработать
SqliteDb.open: (file: String) -> ?SqliteDb = sqlite3("sqlite3_open")
db = SqliteDb.open("test.db")
match db {
Some(db) => db.exec("SELECT 1"),
None => print("Ошибка открытия")
}
// Соглашение не возвращать null → не помечать, при null panicC возвращает null либо пользователь обрабатывает (?T), либо panic. Третьего "тихо игнорировать" нет.
4.6 Обработка ошибок уничтожения
Возвращаемое значение функции, привязанной к .drop, определяет поведение:
Тип возврата .drop | Поведение |
|---|---|
Void | Без ошибок |
Int32 (код ошибки) | Ненулевое → panic — ошибка уничтожения означает ненормальное состояние, лучше обнаружить чем молчать |
?Error | Не None → panic — аналогично |
Ошибка уничтожения не должна замалчиваться. Если нужно игнорировать определённые ошибки, явно обработайте их в обёрточной функции, привязанной к .drop.
5. Поведение FFI в блоках spawn
Определение типа ресурса определяется привязкой .drop (RFC-024), без дополнительных标记:
| Определение | Поведение |
|---|---|
Непрозрачный дескриптор с .drop | Тип ресурса — операции с одним экземпляром в блоке spawn автоматически сериализуются |
Непрозрачный дескриптор без .drop | Не тип ресурса — можно параллельно (чистый дескриптор данных, без побочных эффектов освобождения) |
| Прозрачный тип / тип значения | Не тип ресурса — можно параллельно |
SqliteDb.drop = SqliteDb.close // → тип ресурса
(a, b) = spawn {
r1 = db.exec("SELECT ..."), // тот же экземпляр, автоматическая сериализация
r2 = db.exec("INSERT ...") // ожидание r1
}
(x, y) = spawn {
db1 = SqliteDb.open("a.db"), // разные экземпляры, можно параллельно
db2 = SqliteDb.open("b.db")
}Типы с .drop автоматически сериализуют операции с одним экземпляром в spawn, гарантируя уничтожение без гонок параллелизма.
6. Люк безопасности: сырые указатели + unsafe
Маршалинг по умолчанию через копирование во временную зону безопасен, но имеет накладные расходы memcpy. В сценариях, чувствительных к производительности (большие структуры, частые вызовы), когда нужно копирование без копирования, пользователь явно использует сырой указатель через люк безопасности:
// C напрямую читает память YaoXiang, ноль копирования — пользователь явно принимает риск
ptr: *const U8 = Native.c("libc")("getenv")("HOME")
unsafe {
value = read_c_string(ptr) // пользователь гарантирует действительность ptr
}unsafe используется только для операций с сырыми указателями, полностью ортогонален непрозрачным дескрипторам и прозрачным типам. Обычный FFI (дескрипторы + прозрачные типы) не требует unsafe. Написание unsafe {} = пользователь явно подписывается на риск прямого доступа к памяти.
Граница доверия: C не может предоставить типы контрактов, верифицируемые на этапе компиляции (.h — это не ABI-контракт, таблица символов содержит только имена без сигнатур). Поэтому правильность C-сигнатур не может быть автоматически верифицирована — это гарантируется автором привязки при написании Native.c(...) + сигнатуры. Доверие локализовано в объявлении привязки: автор привязки гарантирует, пользователи пакета получают безопасный API. Это согласовано с Rust extern "C" (написание extern — это акт доверия, после оборачивания в безопасную обёртку вызов безопасен).
Компромиссы
Преимущества
- Полная информация:Компоновка библиотеки на этапе компиляции, верификация символов на этапе компиляции, нет размытости "библиотека не найдена" во время выполнения
- Явная принадлежность распределения:Дихотомия типов, фиксируется при определении, без выводов во время выполнения
- Структурная безопасность:Изоляция через временную зону + Move + RAII, внешний код не касается объектов кучи YaoXiang
- Ноль новых ключевых слов:
Native.cкаррирование +name: type = value, всё переиспользует существующий синтаксис - Честные границы:Не притворяемся, что можем верифицировать C-сигнатуры, доверие локализовано в объявлениях
Недостатки
- Накладные расходы memcpy:Маршалинг по умолчанию с копированием, для больших структур и частых вызовов нужно явно использовать люк безопасности
- Гарантия распределения ручная:Соответствие распределения прозрачного типа и C struct обеспечивается автором привязки/yx-bindgen
- C-сигнатуры не верифицируются на этапе компиляции:Фундаментальное ограничение FFI, YaoXiang не может устранить из C
Стратегия реализации
Этап 1: Внешние библиотеки и символы (v0.8)
- [ ] Реализовать
Native.c("lib")компоновку на этапе компиляции + возврат парсера значений - [ ] Реализовать применение парсера символов (
lib("symbol")) + верификацию таблицы символов на этапе компиляции - [ ] Реализовать дихотомию типов (непрозрачные дескрипторы / прозрачные типы)
- [ ] Реализовать связывание методов (прямое связывание + позиционное связывание
[N])
Этап 2: Маршалинг и безопасность (v0.8)
- [ ] Реализовать генерацию кода маршалинга, управляемую сигнатурой
- [ ] Реализовать изоляцию через копирование во временную зону (копирование входных параметров, возврат через memcpy)
- [ ] Реализовать временное представление String только для чтения + возврат с копированием
- [ ] Реализовать ограничение жизненного цикла заимствования одним вызовом
Этап 3: Владение и жизненный цикл (v0.9)
- [ ] Реализовать Move непрозрачных дескрипторов + единоличное владение
- [ ] Реализовать RAII автоуничтожение
.drop(опционально, отсутствие не вызывает ошибку) - [ ] Реализовать отслеживание потребления (отключение после Move)
- [ ] Реализовать интеграцию
?Tс возвратом null - [ ] Реализовать сериализацию типа ресурса в spawn
Последующая работа
- Расширяемый механизм FFI (RFC-026a):Абстракция
FfiMechanism, плагины.wasm/.pythonи др., динамическая загрузка - yx-bindgen (RFC-026b):Заголовочные файлы C → привязки
.yx+ генерация распределения с учётом платформы
Связь с другими RFC
- RFC-004:Каррирование с несколькими позициями — источник синтаксиса позиционного связывания методов
[N] - RFC-007:Унификация синтаксиса определения функций — привязка
Native.c(...)этоname: type = value - RFC-009:Модель владения — Move, RAII,
?T, жизненный цикл дескрипторов полностью на этом основан - RFC-010:Унифицированный синтаксис типов — типовая аннотация LHS определяет привязку как тип или функцию
- RFC-024:Модель параллелизма — определение типа ресурса в spawn основано на
.drop - RFC-020/021 (устаревшие):Содержимое объединено в данный документ
- RFC-026a:Расширяемая система механизмов FFI
- RFC-026b:Инструментарий yx-bindgen
Журнал принятия решений
| Решение | Решение | Причина | Дата |
|---|---|---|---|
| Библиотека как значение | Native.c("lib") каррирование возвращает парсер | Информация о библиотеке становится видимым на этапе компиляции значением первого класса, заполняет пробел "какую библиотеку компоновать", ноль новых ключевых слов | 2026-07-03 |
| Компоновка на этапе компиляции | Native.c("lib") вызывает -llib | Таблица символов читаема на этапе компиляции, существование символов верифицируемо, типы реальны | 2026-07-03 |
| Дихотомия типов | Непрозрачные дескрипторы / прозрачные типы | Принадлежность распределения二分全覆盖; удалён патч "трёхуровневая модель памяти" | 2026-07-03 |
| Изоляция через временную зону маршалинга | По умолчанию копирование, куча изолирована от внешней стороны | Выход за границы/висячая ссылка внешней стороны повреждает только временную зону, объект YaoXiang цел; копирование ноль требует явно использовать люк | 2026-07-03 |
.drop опционально | При отсутствии ничего не делает, не ошибка | Хранение дескриптора YaoXiang автоматически освобождается; очистка внешних ресурсов — это вопрос спецификации внешней стороны, не берём полномочия强制 | 2026-07-03 |
| Механизм защиты от утечек | Move + единоличное владение дескриптором (безусловно) | Структурная гарантия, не связана с .drop | 2026-07-03 |
| Граница доверия | В объявлении Native.c(...) | C-сигнатуры не верифицируются на этапе компиляции, доверие локализовано, unsafe только для сырых указателей | 2026-07-03 |
| Обработка Null | ?T или panic | Проблемы C не скрываются, нет варианта "тихо игнорировать" | 2026-07-03 |
| Ошибка уничтожения | Тип возврата .drop определяет,统一ный panic | Ошибка уничтожения не должна замалчиваться | 2026-07-03 |
Список литературы
Официальная документация YaoXiang
- RFC-004 Каррирование с несколькими позициями
- RFC-007 Унификация синтаксиса определения функций
- RFC-009 Модель владения
- RFC-010 Унифицированный синтаксис типов
- RFC-024 Модель параллелизма
- RFC-026a Расширяемая система механизмов FFI
- RFC-026b Инструментарий yx-bindgen
Внешние ссылки
Жизненный цикл и судьба
| Статус | Расположение | Описание |
|---|---|---|
| На проверке | docs/design/rfc/review/ | Открыто для обсуждения сообщества |
| Принято | docs/design/rfc/accepted/ | Официальный проектный документ |
