Skip to content

RFC-009: Модель владения ​

Резюме ​

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

Ключевая идея — пять концепций, один градиент:

Взглянуть/изменить     Забрать         Совместное      Скопировать     Системный
на месте                              владение         копию           уровень
    │                  │                  │              │              │
   &T                Move              ref          clone()        unsafe
  &mut T            Без копирования     Rc/Arc          *T
  Токен             по выбору
  нулевого          компилятора
  размера
  Свойства типа
  естественно
  выводят
  права
  • 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      →  Нулевой размер, замораживает исходные данные (пока жив ReadToken, WriteToken запрещён),
          под гарантией замораживания несколько представлений только для чтения безопасны → копируем (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)

# ✅ 正确方式二:上下文在创建点固化(柯里化)——闭包只吃参数,不捕获
gt: (t: Int) -> (x: Int) -> Bool = (x) => x > t
evens = list.filter(nums, gt(threshold))

Дополнение (2026-08-17): правильное решение для зависимости от контекста — фиксация через каррирование, а не захват. После побега замыкания область видимости в точке его определения может уже не существовать, поэтому неявный захват запрещён; но область видимости в точке вызова (точка создания) гарантированно жива, и фиксация контекста в этой точке в виде значения, попадающего в замыкание, безопасна. См. SPEC §12.3.

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

Межзадачное взаимодействие — токены не пересекают границу:

yaoxiang
# ❌ 令牌不能跨任务边界
bad_task: (p: &Point) -> Void = {
    spawn { print(p.x) }          # ❌ 编译错误:令牌不能跨任务传递
}

# 这不是特殊规则——令牌是编译期权限证明,跨任务共享请使用 ref
# 如果需要跨任务共享,请使用 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 в конфигурации проекта, включив в шлюз качества CI.

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

УровеньПоведениеСценарий
allowБез проверкиЛичный проект
warn (по умолчанию)Компиляция проходит, есть предупреждениеЭтап разработки
denyСбой компиляцииКомандный шлюз качества 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Без проверкиПраво пользователя, единообразное освобождение по завершении задачи
Межзадачный цикл reflint (по умолчанию warn)Предупреждение о необходимости переосмысления, настраивается на deny

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

yaoxiang
use std.weak

# 高级用户显式选择
a.next = ref b
b.prev = std.weak.new(a.next)   # 用户显式控制哪个方向是弱的

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

Редакция 2026-08-03: реализовано как отдельный модуль std.weak (std.rc не существует — ref — ключевое слово языка, а не модуль; путь модуля унифицирован как std.weak, точки входа конструирования / обновления — std.weak.new / std.weak.upgrade). Первоначально задуманный в черновике std.rc.Weak не реализован, действует данная редакция.

3.5 Токены заимствования против 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 копируемый токен      Передача владения   ref Rc/Arc     clone()
  &mut T линейный токен    по умолчанию        компилятор      Явное
                            Цепное потребление  автоматически   глубокое
                            с возвратом         выбирает        копирование
   │                          │                  │                │
  Область видимости        Внутри области     Между областями  В любой
  значения токена            видимости         видимости        момент
  Можно вернуть/             T -> T возврат     ref межзадачный  Независимая
  хранить в структуре       T -> Void          → Arc            копия
  Нулевой размер,           потребление        ref внутризадачный
  исчезает после                              → Rc
  компиляции                                  Внутризадачные
                                              циклы молча
                                              разрешены
                                              Межзадачные циклы
                                              — lint
                                              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Не справляется с конкурентным совместным использованием
Без сырых указателейНевозможно системное программирование
Показывать 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 использует только явные параметры, не захватывает внешние переменные неявно; контекст фиксируется через каррирование в точке создания (SPEC §12.3)Область видимости в точке определения замыкания может уже не существовать; фиксированное значение в точке создания (область вызова жива) безопасно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, генерирующим предикаты, а не выполняющим проверку. Это полностью параллельно концепции «проверщика типов»: проверщик типов генерирует предикаты равенства типов, генератор предикатов заимствования генерирует предикаты заимствования, верификация выполняется одним конвейером.

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

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

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

Ссылки ​

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

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


Жизненный цикл и размещение ​

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