Skip to content

RFC-009: Проектирование модели владения

Аннотация

В данном документе определяется модель владения (Ownership Model) языка программирования YaoXiang.

Основной дизайн — пять концепций, один градиент:

Взглянуть/Изменить     Забрать           Общее владение       Скопировать        Системный уровень
     │                    │                    │                   │                   │
    &T                 Move                ref               clone()            unsafe
  &mut T               Нулевое           Компилятор           Явное              *T
  Токен                копирование       автоматически        глубокое          Пользователь
  нулевого             По умолчанию      выбирает             копирование        ответственен
  размера              С RAII            Rc/Arc
  Типовая
  атрибутика
  естественно
  выводит права
  • Move (по умолчанию): присваивание/передача параметров/возврат = передача владения, нулевое копирование, автоматическое освобождение через RAII
  • &T / &mut T (токены заимствования): токены типов времени компиляции нулевого размера. &T может копироваться (общее чтение), &mut T линеен (монопольная запись). Права естественно выводятся из атрибутов типа, без специальных правил. Можно возвращать, можно хранить в структурах.
  • ref ключевое слово: обмен данными между областями видимости. Компилятор автоматически выбирает Rc (не между задачами) или Arc (между задачами)
  • clone(): явное глубокое копирование
  • unsafe + *T: голые указатели, аварийный выход на системный уровень

Устранённая сложность:

  • ❌ Нет времени жизни 'a
  • ❌ Нет отдельной системы проверки заимствований (конфликт заимствований сводится к пропозиции Хоара, совместно с конвейером доказательств проверки типов)
  • ❌ Нет GC
  • ❌ Нет специальных правил типа «запрет на побег» (токены — обычные типы, области видимости обрабатываются единой системой типов)
  • ❌ Пользователю не нужно знать разницу между Rc и Arc (компилятор выбирает автоматически)

Программная нагрузка: &T может копироваться, &mut T не может копироваться — два атрибута типа, ноль специальных правил, полная автоматизация от компилятора. Гарантия производительности: Move с нулевыми накладными расходами, токены с нулевыми накладными расходами (типы нулевого размера, исчезают после компиляции), ref по требованию, нет пауз GC.

Мотивация

Зачем нужна модель владения?

ЯзыкУправление памятьюПроблемы
C/C++Ручное управлениеУтечки памяти, висячие указатели, двойное освобождение
Java/PythonGCЗадержки, накладные расходы памяти, непредсказуемые паузы
RustВладение + проверка заимствованийКрутая кривая обучения времени жизни 'a
YaoXiangMove + Token + refПросто, детерминировано, без GC

Цели проектирования

yaoxiang
# 1. Move по умолчанию (нулевое копирование)
p = Point(1.0, 2.0)
p2 = p                         # Move, p больше нельзя читать

# 2. &T / &mut T токены заимствования (нулевые накладные расходы, атрибуты типа естественно выводят права)
print_info(p2)                 # Компилятор автоматически создаёт токен &Point, освобождает после использования
shift(p2, 1.0, 1.0)           # Компилятор автоматически создаёт токен &mut Point

# 3. ref = обмен (компилятор автоматически выбирает Rc/Arc)
shared = ref p2                # Владение между областями видимости
spawn { use(shared) }          # Компилятор: между задачами → Arc

# 4. clone() = явное копирование
backup = p2.clone()            # Глубокое копирование, независимая копия

# 5. unsafe + *T = системный уровень
unsafe {
    ptr: *Point = &p
    (*ptr).x = 0.0
}

Ключевые отличия от Rust

ХарактеристикаRustYaoXiang
Семантика по умолчаниюЗаимствование &T (нужно явное .clone())Move (передача значений, нулевое копирование)
Заимствование&T/&mut T, можно возвращать, нужны времена жизни**&T/&mut T токены нулевого размера, атрибуты типа Dup/Linear естественно выводят права
Механизм обменаArc::new() + ручной Weakref ключевое слово (компилятор автоматически выбирает Rc/Arc)
Копированиеclone()clone()
Голые указатели*T*T
Время жизни'a❌ Нет
Проверка заимствованийГлобальный выводГенерация пропозиций заимствования автоматическим проверщиком типов, унифицированный конвейер проверки
Циклические ссылкиРучной WeakОсвобождение в конце задачи / lint для межзадачных / стандартный Weak

Предложение

1. Move (передача владения по умолчанию)

yaoxiang
# Правило: присваивание / передача параметров / возврат = Move, нулевое копирование

p: Point = Point(1.0, 2.0)
p2 = p                           # Move, p больше нельзя читать

# Переменной можно переприсвоить (стиль Python, без затенения)
p = Point(3.0, 4.0)              # Перепривязка p, тип должен совпадать

# Параметры функции: Move
process: (p: Point) -> Point = {
    p.transform()
    p                            # Move возврат
}

# Возврат из функции: Move
create: () -> Point = {
    p = Point(1.0, 2.0)
    p                            # Move возврат, нулевое копирование
}

Характеристики:

  • Нулевое копирование (компилятор перемещает указатель)
  • После перемещения исходная привязка недоступна для чтения (ошибка компиляции)
  • RAII: автоматическое освобождение при выходе из области видимости
  • Сигнатура функции (T) -> T сама по себе является документацией — потребляет T, возвращает T

2. &T / &mut T (токены заимствования)

Основной принцип: &T и &mut T — это токены типов времени компиляции нулевого размера. Это не «ссылки», а «типизированные доказательства прав доступа».

2.1 Два атрибута типа

&T      →  Нулевой размер, замораживает данные источника (запрет WriteToken при живом ReadToken),
           безопасность множественных представлений только для чтения при заморозке → Можно копировать (Dup)
&mut T  →  Нулевой размер, монопольный доступ для чтения/записи (запрет любых других токенов при живом WriteToken),
           копирование бессмысленно при монопольном доступе → Линеен (не Dup)

Причинно-следственная связь не может быть обратной: заморозка — причина, Dup — следствие. Не потому что &T реализует Dup, можно сосуществовать — а потому что данные заморожены (нет возможности мутации), множественные представления только для чтения безопасны, Dup может быть реализован. Если принять Dup за определение, а проверку конфликтов за «дополнительный патч» — дизайн неправильный.

2.2 Базовое использование

yaoxiang
# На стороне метода: объявление типа параметра, определяющего необходимые права
Point.print: (self: &Point) -> Void = {
    print(self.x)                  # Токен &Point даёт права на чтение
    print(self.y)
}

Point.shift: (self: &mut Point, dx: Float, dy: Float) -> Void = {
    self.x = self.x + dx           # Токен &mut Point даёт права на запись
    self.y = self.y + dy
}

# На стороне вызова: компилятор автоматически выбирает заимствование или Move
p = Point(1.0, 2.0)
p.print()                          # Компилятор автоматически создаёт токен &Point
p.shift(1.0, 1.0)                  # Компилятор автоматически создаёт токен &mut Point
p.print()                          # OK, предыдущий токен освобождён после завершения вызова shift

# Для свободных функций аналогично
distance: (a: &Point, b: &Point) -> Float = {
    sqrt((a.x - b.x)**2 + (a.y - b.y)**2)  # Два токена &Point сосуществуют — тип Dup
}
d = distance(p, p2)

2.3 Почему не нужно «запрет на побег»

В RFC-009 v8 для &T/&mut T применялись три специальных правила — только параметры, нельзя возвращать, нельзя хранить в структурах. Это патчи для концепции «заимствования».

Токенная система не нуждается в этих правилах. Токены — обычные типы, подчиняющиеся тем же правилам области видимости, что и все остальные типы.

Возврат ссылок — естественно поддерживается:

yaoxiang
# ✅ Токен распространяется вместе с возвращаемым значением
Point.get_x: (self: &Point) -> (&Float, &Point) = {
    return (&self.x, self)  # Субтокен и родительский токен возвращаются вместе
}

# Использование
p = Point(1.0, 2.0)
(px_ref, p) = p.get_x()    # Токен возвращается вызывающему
print(px_ref)               # OK, токен всё ещё в области видимости

Хранение в структурах — естественно поддерживается:

yaoxiang
# ✅ Структура содержит токен как поле
Window: Type = {
    target: Point,
    view: &Point,      # Поле токена — держит представление только для чтения для target
}

# Токен view происходит от target, Window владеет обоими
# Пока Window существует, токен view валиден

2.3 Замыкания и явные параметры Lambda

Lambda — это значение-функция — может быть возвращено, сохранено, передано за пределы текущей области видимости. Поэтому Lambda не неявно захватывает внешние локальные переменные. Когда нужны внешние данные, используются явные параметры:

yaoxiang
# ✅ Lambda использует явные параметры
double: (x: Int) -> Int = (x) => x * 2
filter_by: (items: List(Int), f: (Int) -> Bool) -> List(Int) = { ... }

# ✅ spawn { } не подчиняется этому правилу — spawn это немедленно выполняемый параллельный блок, родительская задача блокируется в ожидании
shared = ref data
spawn { use(shared) }

# ❌ Lambda не может неявно захватывать внешние переменные
x = 42
f = () => { x + 1 }  # Ошибка компиляции: x не в области видимости

# ✅ Правильный способ: явная передача параметра
f = (x) => { x + 1 }
f(x)

spawn { } — это не значение-функция. Блок, отмеченный spawn, как и тело if/while — выполняется немедленно, завершается пока жив стековый кадр родителя. Тело spawn может нормально обращаться к внешним переменным.

Между задачами — токены не могут проходить через потоки:

yaoxiang
# ❌ Токены не могут пересекать границы задач
bad_task: (p: &Point) -> Void = {
    spawn { print(p.x) }          # ❌ Ошибка компиляции: токены нельзя передавать между задачами
}

# Это не специальное правило — токены это доказательства прав доступа времени компиляции,
# для обмена между задачами используйте ref

Токены нельзя оборачивать в ref:

yaoxiang
# ❌ Токены это доказательства прав, не владение
bad_ref: (p: &Point) -> Void = {
    shared = ref p                # ❌ Ошибка компиляции: &T не является владеющим типом
}

2.4 Жизненный цикл токенов

Жизненный цикл токенов определяется обычными правилами области видимости, параметры времени жизни не нужны:

  • Токены в параметрах функции: живут во время вызова, освобождаются после завершения вызова
  • Возвращаемые токены: владение передаётся вызывающему
  • Токены, хранящиеся в структурах: живут вместе со структурой

Компилятору не нужны аннотации 'a, потому что токены — значения, жизненный цикл значений управляется системой владения (Move/RAII). Сведение проблемы заимствования к проблеме владения.

2.5 Обнаружение конфликтов токенов

Обнаружение конфликтов токенов — это пропозиции логики Хоара, а не независимый потоково-чувствительный анализ.

{все конфликтующие ReadToken мертвы} write(data) {WriteToken безопасно получен}

Разделяет конвейер доказательств RFC-027 с проверкой типов и пользовательскими предикатами. Компилятор автоматически генерирует пропозиции заимствования (borrow_conflict, use_after_move, use_after_drop, mut_violation), передаёт в конвейер. Конвейер возвращает Proved / Disproved / Unproven.

yaoxiang
# ❌ Токен &mut линеен, нельзя копировать
bad_dup: (p: &mut Point) -> Void = {
    p2: &mut Point = p              # Move, p больше нельзя читать
    p.x = 10.0                      # ❌ Ошибка компиляции: WriteToken уже перемещён
}

# ✅ Токен &T имеет тип Dup, можно свободно копировать
good_dup: (p: &Point) -> Void = {
    p2: &Point = p                  # OK, &T имеет тип Dup
    print(p.x)                      # OK
    print(p2.x)                     # OK, два токена только для чтения сосуществуют
}

Проверка заимствований не исчезла — она свелась. Существующий BorrowChecker становится BorrowPredicateEmitter (генератором пропозиций), генерируемые пропозиции заимствования и другие типовые пропозиции разделяют один конвейер доказательств. Это полностью параллельно концепции проверщика типов: проверщик типов генерирует типовые уравнения, генератор пропозиций заимствования генерирует пропозиции заимствования — один конвейер проверки. Детальный дизайн см. в RFC-009a.

2.7 Внутреннее устройство компилятора: механизм брендов

Пользователь никогда не сталкивается с брендами. Компилятор внутренне назначает каждому токену уникальный идентификатор времени компиляции:

Что видит пользователь     Внутреннее представление компилятора
────────────────────────────────────────
&Point                  →  ReadToken(Point, #N)    // #N — уникальное целое число времени компиляции
&mut Point              →  WriteToken(Point, #M)   // #M — уникальное целое число времени компиляции

Назначение брендов:

  • Защита от подделки: токены можно получить только от владеющего владельца, нельзя создать из воздуха
  • Отслеживание происхождения: при получении &Float из &Point (доступ к полю), &Float несёт производный бренд (#N.field_x), компилятор может отследить до родительского токена
  • Обнаружение конфликтов: одноисходный WriteToken и производный ReadToken не могут быть живы одновременно

Бренды полностью исчезают после мономорфизации и инлайнинга, в генерируемом машинном коде их нет. Нулевые накладные расходы во время выполнения.

2.8 Правила автоматического выбора заимствования

Компилятор на стороне вызова автоматически выбирает по следующему приоритету:

1. Если фактический аргумент ещё используется в дальнейшем → приоритет создания токена (&T или &mut T, согласно сигнатуре метода)
2. Если фактический аргумент больше не используется → Move
3. Приоритет соответствия: &T < &mut T < Move
yaoxiang
# Пример: автоматический выбор
p = Point(1.0, 2.0)
p.print()        # print объявляет &self → компилятор создаёт токен &Point
p.shift(1.0, 1.0) # shift объявляет &mut self → компилятор создаёт токен &mut Point
p2 = p           # Move, p больше не используется

2.9 Сравнение с упрощённым заимствованием из RFC-009 v8

ХарактеристикаУпрощённое заимствование (v8)Токены заимствования (v9)
Возврат ссылок❌ Жёсткий запрет✅ Токен распространяется с возвращаемым значением
Хранение в структурах❌ Жёсткий запрет✅ Токен как поле структуры
Явные параметры Lambda❌ Жёсткий запрет✅ Lambda использует явные параметры
Специальные правила3 (только параметр/нельзя возвращать/нельзя хранить)0 — атрибуты типа естественно выводят
Проверка заимствованийСпециализированная перекрёстная проверкаПотоково-чувствительный анализ активности проверщика типов
Аннотации времени жизниНе нужныНе нужны
Накладные расходы выполненияНольНоль (типы нулевого размера, исчезают после компиляции)
Сообщения об ошибках«Заимствование не может выйти»«WriteToken(#3) уже перемещён» (обычная ошибка типа)
Ментальная модель пользователяПонимание «особого статуса заимствования»&T можно копировать, &mut T нельзя копировать

3. ref ключевое слово (автооптимизация компилятора)

ref — единственный способ обмена данными между областями видимости. Нижний уровень Rc или Arc — пользователю не нужно об этом думать.

3.1 Базовое использование

yaoxiang
p: Point = Point(1.0, 2.0)
shared = ref p                   # Общее владение, компилятор автоматически выбирает реализацию

# Обмен между задачами
@block
main: () -> Void = {
    data = ref heavy_data
    spawn { use(data) }           # Компилятор: между задачами → Arc
    spawn { use(data) }           # Компилятор: между задачами → Arc
}

# Обмен в рамках одной задачи
@block
main: () -> Void = {
    data = ref heavy_data
    use(data)                     # Компилятор: не между задачами → Rc
}

Ментальная модель пользователя: ref = общее владение. Этого достаточно.

3.2 Анализ побега компилятора: Rc vs Arc

Анализ потока данных ref:

Не сбегает в другие задачи → Rc (неатомарный подсчёт ссылок, низкие накладные расходы)
Сбегает в другие задачи    → Arc (атомарный подсчёт ссылок, потокобезопасность)

3.3 Стратегия обнаружения циклов

Циклы внутри задачи → Безмолвно разрешены.
  ├── У каждой задачи чёткая граница жизненного цикла — в конце задачи все ресурсы (включая ref циклы) освобождаются вместе.
  ├── Долгоживущие сервисы должны создавать подзадачи по запросам/соединениям — подзадача завершается, ресурсы автоматически возвращаются, не накапливаются.
  ├── ref всегда остаётся живым, семантика без разбавления.
  └── Пользователь имеет право строить двунаправленные сильные ссылки внутри задачи (например, промежуточные состояния графовых вычислений).

Циклы между задачами → lint (по умолчанию warn, настраивается).
  ├── Программа работает корректно, настоящих утечек не будет (при завершении родительской задачи все ресурсы подзадач освобождаются).
  ├── Но межзадачные сильные ссылки означают размытые границы владения — стоит остановиться и переосмыслить.
  ├── По умолчанию уровень warn, компиляция проходит с предупреждением.
  └── Команда может установить deny в конфигурации проекта, включить в quality gate CI.

Уровни lint (по аналогии с Rust clippy):

УровеньПоведениеСценарий
allowНе проверятьЛичный проект
warn (по умолч.)Компиляция проходит, есть提示Этап разработки
denyКомпиляция не проходитQuality gate команды в CI
forbidКомпиляция не проходит, нельзя переопределитьОрганизационное обязательное правило
yaoxiang
# Цикл внутри задачи: безмолвно разрешён, двунаправленные сильные ссылки
build_graph: () -> Void = {
    a = Node("a")
    b = Node("b")
    a.next = ref b
    b.prev = ref a                # Цикл. В конце задачи освобождается вместе.
}

# Цикл между задачами: lint (по умолчанию warn)
@block
parent_task: () -> Void = {
    shared_a = ref a
    shared_b = ref b
    spawn {
        shared_a.child = ref shared_b   # ⚠️ warn: циклическая ссылка между задачами
    }
}

Пример конфигурации проекта:

toml
# yaoxiang.toml
[lints]
cross-task-cycle = "deny"    # Межзадачные циклы отклоняются в CI напрямую
Тип циклаПоведениеПричина
Внутризадачный ref циклНе проверяетсяПраво пользователя, освобождение в конце задачи
Межзадачный ref циклlint (по умолчанию warn)Напоминание переосмыслить, можно настроить deny

3.4 Weak: предоставляется стандартной библиотекой

yaoxiang
use std.rc.Weak

# Продвинутый пользователь делает явный выбор
a.next = ref b
b.prev = Weak.new(a.next)        # Пользователь явно контролирует, какая сторона слабая

Weak не встроен в язык, это тип стандартной библиотеки. Для повседневного использования достаточно ref. Продвинутые пользователи, которым нужна тонкая настройка управления памятью, вручную импортируют Weak.

3.5 Токены заимствования vs ref

&T / &mut Tref
Что делаетВзглянуть / изменить на местеОбщее владение
Область действияПо области видимости значения токенаМежду областями видимости
СтоимостьНоль (типы нулевого размера)Rc или Arc (выбирает компилятор)
ПобегМожно (токен распространяется с возвратом/структурой/замыканием)Это его основное назначение
Между задачамиНельзя (токен — доказательство прав времени компиляции, нельзя передавать между задачами)Можно (компилятор автоматически выбирает Arc)
Образование цикловНе применимоВнутри задачи безмолвно, между задачами lint

4. clone() —— Явное копирование

yaoxiang
p: Point = Point(1.0, 2.0)
p2 = p.clone()                   # Глубокое копирование
# p и p2 независимы, не влияют друг на друга

Когда использовать: когда нужно сохранить оригинальное значение и сценарий не подходит для Move или совместного использования.

5. unsafe + Голые указатели (системное программирование)

yaoxiang
p: Point = Point(1.0, 2.0)

unsafe {
    ptr: *Point = &p              # Голый указатель
    (*ptr).x = 0.0                # Разыменование (пользователь гарантирует безопасность)
    ptr2 = ptr + 1                # Арифметика указателей
}

Ограничения:

  • Можно использовать только в блоках unsafe
  • Пользователь гарантирует отсутствие висячих указателей и использования после освобождения
  • Используется для FFI, операций с памятью и другого системного программирования

6. Общий обзор градиента владения

  Токены заимствования (ноль накладных)    Move (ноль накладных)    Обмен (по потребности)  Копирование
           │                                   │                        │                 │
          &T                                  Move                   ref Rc/Arc            clone()
        &mut T                              По умолчанию            Компилятор             Явное
          │                              передача владения         автоматически          глубокое
          │                                   │                     выбирает               │
          │                              Цепное потребление         обмен                  │
          │                              и возврат                                        │

   Область видимости                    В области видимости       Между областями       В любой момент
   значения токена                                             видимости
   Можно возвращать/                     T -> T возврат         ref между задачами → Arc
   хранить в структурах                                        ref не между задачами → Rc
   Нулевой размер,                                                 Внутри задачи безмолвно
   исчезает после                                                Между задачами lint
   компиляции                              T -> Void              Стандартный Weak
   нулевой размер,                        потребление             аварийный выход
   исчезает после
   компиляции

Комплексный пример

yaoxiang
Point: Type = {
    x: Float,
    y: Float,

    # &T: токен только для чтения
    print: (self: &Point) -> Void = {
        print(self.x)
        print(self.y)
    }

    # &mut T: изменяемый токен
    shift: (self: &mut Point, dx: Float, dy: Float) -> Void = {
        self.x = self.x + dx
        self.y = self.y + dy
    }

    # Move → Move: потребление и возврат
    scale: (self: Point, f: Float) -> Point = {
        self.x = self.x * f
        self.y = self.y * f
        self                            # Забрать, изменить, вернуть
    }

    # Возврат ссылки: токен распространяется с возвращаемым значением
    get_x: (self: &Point) -> (&Float, &Point) = {
        return (&self.x, self)
    }
}

# Lambda с явными параметрами
double: (x: Int) -> Int = (x) => x * 2

# Комплексное использование
p = Point(1.0, 2.0)
p.print()                           # Токен &Point
p.shift(1.0, 1.0)                   # Токен &mut Point
p = p.scale(2.0)                    # Move → возврат
shared = ref p                      # ref обмен
spawn { use(shared) }

# clone независимая копия
backup = p.clone()

# Цикл внутри задачи: безмолвно разрешён
a = Node("a")
b = Node("b")
a.next = ref b
b.prev = ref a                      # Цикл, освобождается в конце задачи

# unsafe системный уровень
unsafe {
    ptr: *Point = &p
    (*ptr).x = 0.0
}

Ограничения системы типов

Атрибут типа Dup

Dup (Duplicable) — это атрибут типа, автоматически управляемый компилятором, означающий поверхностное копирование: при присваивании/передаче параметров копируется дескриптор/токен, базовые данные разделяются. Это формирует трёхуровневый градиент с Move (передача владения) и Clone (явное глубокое копирование, создание независимой копии).

Dup и Clone — ортогональные концепции — Dup копирует дескриптор с разделением данных, Clone создаёт независимую копию. Тип может одновременно поддерживать Dup и Clone, или только один из них.

ТипDupCloneОписание
&T✅ (копирование токена, несколько представлений указывают на одни данные)Токен только для чтения
ref T✅ (счётчик ссылок+1, куча данных разделяется)Общее владение (компилятор выбирает Rc/Arc)
String, Bytes✅ (внутренний счётчик ссылок, копирование дескриптора разделяет базовый буфер)Строка/байты
&mut T❌ (линеен, монопольный)Изменяемый токен
*TГолый указатель
structПроизводный (автоматически производится, когда все поля Dup)Структура

Примитивные типы значений (Int, Float, Bool, Char) при присваивании ведут себя как встроенное копирование значений компилятором — два полностью независимых значения. Они не принадлежат к атрибуту типа Dup, а являются нативной обработкой компилятора.


Анализ производительности

ОперацияСтоимостьОписание
MoveНольПеремещение указателя
&T / &mut TНольТипы нулевого размера, исчезают после компиляции, нулевые накладные расходы выполнения
ref (не между задачами)НизкаяКомпилируется в Rc, неатомарные операции
ref (между задачами)СредняяКомпилируется в Arc, атомарные операции
clone()Зависит от типаМаленькие объекты быстро, большие медленно
unsafe + *TНольПрямые операции с памятью

Сравнение

ЯзыкМеханизм обменаУправление памятьюОбработка цикловСложность
RustArc / Mutex + проверка заимствованийВремя компиляцииРучной WeakВысокая
Gochan / pointerGCGCНизкая
C++shared_ptrRAIIweak_ptrСредняя
YaoXiangref + токены заимствованияRAIIОсвобождение на границе задач / межзадачный lint / стандартный WeakНизкая

Компромиссы

Преимущества

  1. Единство: &T/&mut T — обычные типы, не специальная языковая особенность. Полностью согласовано с name: type = value из RFC-010
  2. Простота: без времени жизни, проверка заимствований сводится к пропозициям системы типов. &T можно копировать, &mut T нельзя — два атрибута типа
  3. Мощность: можно возвращать ссылки, хранить в структурах, захватывать в замыканиях — выразительность на уровне Rust
  4. Интеллект компилятора: ref автоматически выбирает Rc/Arc, на стороне вызова автоматический выбор заимствования
  5. Детерминированность: ref это гарантия живучести, не бывает скрытого ослабления
  6. Высокая производительность: Move с нулевым копированием, токены с нулевыми накладными расходами (типы нулевого размера, исчезают после компиляции)
  7. Гибкость: unsafe + *T поддерживает системное программирование

Недостатки

  1. Заражение обобщённых параметров бренда: токены несут идентификаторы брендов, сигнатуры функций с возвратом ссылок содержат дополнительные обобщённые параметры
  2. Накладные расходы выполнения ref: у атомарных операций есть цена (но это неизбежная плата за обмен)
  3. Риски unsafe: пользователь должен гарантировать корректность
  4. Межзадачные циклы — lint, не ошибка компиляции:,不像 Rust那样编译报错,默认 warn,需要团队配置 deny才能作为质量门

Альтернативные решения

РешениеПочему не выбрано
GCНакладные расходы выполнения, непредсказуемые паузы
Проверка заимствований RustНужны времена жизни 'a, крутая кривая обучения
Только MoveНе может обрабатывать параллельный обмен
Без голых указателейНевозможно системное программирование
Expose Rc/Arc пользователюДетали реализации перекладываются на пользователя, увеличивается когнитивная нагрузка
Упрощённое заимствование (v8)Стратегия запрета побега жертвует ключевыми возможностями: захватом замыканий, возвратом ссылок и т.д.

Журнал решений по дизайну

РешениеРешениеПричинаДата
Значение по умолчаниюMove (нулевое копирование)Высокая производительность, нулевые накладные расходы2025-01-15
Механизм обменаref ключевое слово, автооптимизация компилятораПростота для пользователя, компилятор берёт на себя ответственность2025-01-15
Заимствование&T/&mut T как типы токенов нулевого размераАтрибуты типа (Dup/Linear) естественно выводят права, унифицированная система типов2025-01-15
Токены заимствованияЗамена упрощённого заимствования, &T Dup, &mut T LinearУстранение специальных правил типа «запрет на побег», поддержка захвата замыканий/возврата ссылок/хранения в структурах2026-05-29
Копированиеclone()Явная семантика2025-01-15
Системный уровень*T + unsafeПоддержка системного программирования2025-01-15
Время жизниНе реализуетсяТокены это значения, жизненный цикл управляется Move/RAII единообразно, заимствование сводится к проблеме владения2025-01-15
Rc/ArcАвтовыбор компилятора, невидимо для пользователяСнижение когнитивной нагрузки2025-01-15
Циклические ссылкиВнутри задачи не проверяются, между задачами lint (по умолчанию warn)Структурированный параллелизм естественно гарантирует, lint настраивается на deny2025-01-16
WeakПредоставляется стандартной библиотекойПродвинутый пользователь делает явный выбор2025-01-16
Анализ потребленияУдалёнУпрощённый проверщик заимствований, не нужен2026-05-11
Возврат владенияУдалёнСигнатура (T) -> T сама по себе является документацией2026-05-11
Переиспользование пустого состоянияУдалён (как характеристика)Move с последующим переприсваиванием — естественное поведение2026-05-11
Обратная функция/частичное потребление/трёхуровневая изменчивость полейУдалёнЧрезмерное проектирование2026-05-11
Lambda не неявно захватываетLambda использует только явные параметры, не неявно захватывает внешние переменныеЯвная философия, упрощение компилятора2026-06-16

История версий

ВерсияОсновные измененияДата
v1Первый черновик: на основе модели владения Rust2025-01-08
v8Удаление чрезмерного проектирования (обратная функция/частичное потребление/трёхуровневая изменчивость полей/анализ потребления/возврат владения/переиспользование пустого состояния), добавлено упрощённое заимствование &T/&mut T2026-05-11
v9Система токенов заимствования заменяет упрощённое заимствование, унифицированная система типов; корректировка обнаружения конфликтов токенов на пропозиции Хоара, см. RFC-009a2026-06-13

Нерешённые вопросы

ВопросОписаниеСтатус
Синтаксис DropНужна ли явная функция drop()К обсуждению
Алгоритм анализа побегаОбнаружение межзадачности для refК обсуждению
Обнаружение конфликтов токеновПропозиции логики Хоара, см. далее✅ Решено (см. RFC-009a)

Обнаружение конфликтов токенов: пропозиции логики Хоара

Полная схема обнаружения конфликтов токенов описана в RFC-009a: Анализ жизненного цикла токенов — на основе конвейера доказательств Хоара. Ключевые моменты:

Жизненность токена — это пропозиция логики Хоара.{все конфликтующие ReadToken мертвы} write(data) {WriteToken безопасно получен} — разделяет конвейер доказательств RFC-027 с проверкой типов и пользовательскими предикатами. Компилятор автоматически генерирует пропозиции заимствования (borrow_conflict, use_after_move, use_after_drop, mut_violation), конвейер возвращает Proved / Disproved / Unproven.

Проверка заимствований не исчезла — она свелась. BorrowChecker становится BorrowPredicateEmitter, генерирует пропозиции вместо выполнения проверок. Это полностью параллельно концепции «проверщика типов»: проверщик типов генерирует типовые уравнения, генератор пропозиций заимствования генерирует пропозиции заимствования — один конвейер проверки.

ID бренда (#42) и есть 'a. Информация абсолютно идентична, различается кодировка. 'a виден в сигнатуре типа, #42 внутри компилятора. Никакого нового анализа не изобретено — время жизни перенесено с типового уровня на уровень доказательства.

Обзор алгоритма (подробности в RFC-009a):

  • Префиксное сопоставление дерева брендов → определение конфликтующих токенов (O(depth), глубина ≤ 3)
  • Обратный BFS → от потребителя, break обрывает обратные рёбра, структурный анализ покрывает 95%+ сценариев (быстрый путь)
  • Отсечение через SMT-логику → только при while + условиях пути вызывается (медленный путь, крайне редко)

Ссылки

Официальная документация YaoXiang

Внешние ссылки


Жизненный цикл и статус

СтатусРасположениеОписание
Черновикdocs/design/rfc/Черновик автора, ожидает рецензии
На рецензииdocs/design/rfc/Открыто для обсуждения сообщества
Принятоdocs/design/accepted/Становится официальным документом дизайна
Отклоненоdocs/design/rfc/Сохраняется в директории RFC