Valen: автор описал гибкий borrow checker на основе Group Borrowing
Пост в блоге посвящён новому, более гибкому механизму borrow checker (проверка заимствований ссылок на этапе компиляции) в языке Valen. Автор давно ищет «святой Грааль» безопасности памяти: нечто настолько же мощное, как borrow checking в Rust, но при этом простое и гибкое, как подсчёт ссылок или сборка мусора. Он считает, что для этого нужно решить три задачи, а в этом тексте разбирает в основном первую: более мягкий borrow checker.
Основа подхода Valen, предложение Group Borrowing, которое придумал друг автора Nick Smith. Центральная идея: компилятор помнит, на что указывает каждая ссылка, и по этой информации проверяет безопасность программы. Изначально идея создавалась с прицелом на язык Mojo, но, по словам автора, убедить начальство её использовать не удалось. Тогда они с Ником потратили месяцы на то, чтобы отточить объяснение и выявить выгоды, и в прошлом году написали об этом пост в надежде, что подход подхватят другие языки. По словам автора, так и случилось: идея разошлась широко. Большинство этих языков решают одну и ту же задачу, как сделать borrow checker гибче. Valen хочет, чтобы он работал вместе с подсчётом ссылок и generational references (ссылками с поколениями), а Carbon, чтобы он уживался с кодом на C++.
Сложность в том, что между borrow checking и другими подходами существует фундаментальный конфликт. Автор утверждает, что Group Borrowing разрешает его, ослабляя правило «shared-xor-mutable» (либо общий доступ, либо изменение) до правила «no use-after-free» (нет использования памяти после освобождения). По его словам, подход даёт безопасность памяти без затрат во время выполнения и ловит use-after-free на этапе компиляции с меньшим числом ограничений, чем прежние решения. Сам автор оговаривает, что спорно, может ли какой-либо язык получить безопасность памяти с нулевой стоимостью, и это относится и к Group Borrowing.
В примерах показано два случая. В первом компилятор отклоняет программу: ссылка на первый элемент world.entities берётся, затем весь вектор заменяется новым, и обращение к first_ref.hp даёт ошибку «использован заимствованный объект после инвалидации». Во втором на один вектор создаются две ссылки, и через обе в него добавляются элементы, такую программу обычные компиляторы отвергают, а здесь она признаётся безопасной. Автор перечисляет и другие трудные для borrow checker шаблоны, которые должен принимать подход: например, объекты File с общей ссылкой на объект FileSystem или ссылки внутри ScopeGuard, а также возможное основание для пользовательского defer.
Главный пример, функция step, получающая ссылку на World и ссылку на один из его элементов: entity in world.entities[] mut. Ключевое слово in задаёт, куда указывает ссылка, и именно так компилятор узнаёт её «место». Автор сравнивает три версии. В C++ код получается короткий, но безопасность памяти целиком на программисте. В Rust приходится каждый раз заново получать ссылку с проверкой границ и возможной паникой, переделывать программу в более «плоскую» или применять GhostCell с «брендами» и GhostToken. В Valen код выглядит как в C++, оставаясь проверяемым при компиляции. Автор подчёркивает, что такая ссылка не похожа на уникальные ссылки Rust (&mut): на ту же сущность могут существовать и другие ссылки.
Отдельно автор оговаривает ограничения: в сегодняшнем компиляторе приходится вручную разыменовывать, обращаться к массивам напрямую вроде my_vec.data[0] и указывать типы всех параметров. Он объясняет это тем, что работает над чужим языком. От языка Vale язык Valen отличается тем, что оба поддерживают линейные типы, синтаксис пока в основном одинаков, но Valen может сдвинуться в сторону «более простого Rust». Ещё автор вспоминает прежнюю попытку, «hybrid-generational memory»: он прошёл тридцать две версии, прежде чем от неё отказаться.
Ключевые факты
- Borrow checker языка Valen построен на предложении Group Borrowing, которое придумал Nick Smith; идея в том, что компилятор знает, на что указывает каждая ссылка (указывается через ключевое слово
in). - Подход ослабляет правило «shared-xor-mutable» до «no use-after-free»: по словам автора, ошибки использования памяти после освобождения ловятся при компиляции, а допускаются, например, несколько ссылок на чтение и запись на один объект.
- Автор заявляет об отсутствии затрат во время выполнения, но сам признаёт, что спорно, достижима ли у любого языка безопасность памяти с нулевой стоимостью; измерений в тексте нет.
- Функция
step(world &World, entity in world.entities[] mut)в Valen выглядит как код на C++, тогда как в Rust приходится заново получать ссылки с проверкой границ или применять GhostCell. - Компилятор пока сырой: нужны ручное разыменование, прямая индексация массивов и явные типы всех параметров.
Почему это важно
Безопасность памяти без сборщика мусора и без жёстких ограничений Rust, одна из главных задач проектирования языков. Автор считает, что Group Borrowing ослабляет конфликт между borrow checking и подходами вроде подсчёта ссылок, а Valen и Carbon пытаются сделать borrow checker совместимым с этими подходами или с кодом на C++. Это объяснение дизайна, а не релиз: оно показывает, в какую сторону может двигаться проектирование языков с проверкой заимствований.
Кому это важно
Разработчикам языков программирования и компиляторов, а также тем, кто пишет на Rust или C++ и упирается в ограничения borrow checker: например, в необходимость заново получать ссылки на элементы одной структуры, как в примере с step. Читателям, интересующимся безопасностью памяти, подойдёт как обзор того, как автор сравнивает подходы Rust, C++ и Valen.
Как это применить
Напрямую применить пока нечего: в доступной части материала не сказано, выпущен ли Valen для широкого использования. Практическая ценность, в идее. Если проектируете язык или библиотеку, обратите внимание на приём, когда пользователь явно указывает, в какой структуре «живёт» ссылка (in world.entities[]), и компилятор отслеживает только риск use-after-free, а не запрещает все одновременные изменяемые ссылки. Для сравнения подходов автор обещает отдельную статью о том, как работают и чем различаются решения разных языков.
Можно ли доверять
Это авторский разбор от лица человека, который сам строит язык и рассказывает о собственном подходе; утверждения о «нулевой стоимости» и «меньшем числе ограничений» подкреплены примерами кода, но не замерами. Автор сам оговаривает, что нулевая стоимость безопасности памяти спорна. Он также заявляет, что статья написана без ИИ. Доступная часть материала обрывается до подробного объяснения механизма, поэтому проверить саму корректность подхода по этому тексту нельзя; дата публикации в видимом тексте не указана.
Риски и подводные камни
Автор прямо говорит, что компилятор сегодня неудобен: нужно вручную разыменовывать, индексировать массивы напрямую и указывать типы всех параметров, а синтаксис Valen ещё может измениться в сторону «упрощённого Rust». Три задачи безопасности памяти названы, но в этом тексте разбирается только первая; решение остальных пока лишь намечено словами «возможно, Valen их решил». Историю идеи тоже стоит учитывать: Group Borrowing не взяли в Mojo, а предыдущий подход автора пришлось бросить после тридцати двух версий.
«Если вы хотите, чтобы кто-то потратил время на чтение, потратьте время и напишите это вручную.»
— автор статьи, блог verdagon.dev