
Что внутри #: TokenStream, syn, quote и почему этот serde так долго компилируется
morett1m 12 минут назад Что внутри #: TokenStream, syn, quote и почему этот serde так долго компилируется Средний 10 мин 569 Блог компании Beget Rust * Программирование * Высоконагруженные системы * Обзор Привет, Хабр!#...
<5 — 2026'da uzaya kaç SpaceX Starship fırlatması ulaşacak?
Вот важная новость с фронта ИИ: morett1m 12 минут назад Что внутри #: TokenStream, syn, quote и почему этот serde так долго компилируется Средний 10 мин 569 Блог компании Beget Rust * Программирование * Высоконагруженные системы * Обзор Привет, Хабр! # это какая-то одна строка в коде. На холодной сборке за ней прячется двадцать с лишним секунд компиляции, даже если в проекте больше ничего нет.
Откройте cargo build --timings на любом не самом маленьком проекте с serde, и serde_derive почти наверняка окажется в первой тройке самых медленных крейтов. При том что в самом serde_derive всего несколько тысяч строк. Между этой строкой и этими секундами лежит вся инфраструктура процедурных макросов: TokenStream, syn, quote, proc-macro2, watt.
Технические детали
Пройдёмся по ней в этой статье. proc-macros на уровне процессовКогда вы пишете #, на этапе компиляции происходит примерно следующее. Сначала rustc обнаруживает, что для разбора вашего крейта нужен proc-macro из serde_derive.
Прежде чем продолжить парсить ваш код, компилятору нужно serde_derive собрать. Причём собрать целиком, в исполняемый код, а не ограничиться проверкой типов. Ведь proc-macros это не данные, это код, который rustc запустит в своём процессе.
Дальше rustc загружает скомпилированный serde_derive как динамическую библиотеку (. dll на Windows) в свой процесс и вызывает его через так называемый мост (bridge). Устроен мост как пара клиент-сервер: proc-macro выступает клиентом, компилятор сервером, а токены между ними передаются через двоичный интерфейс уровня C (C ABI) и целочисленные хэндлы.
Отраслевые последствия
Зачем такие сложности, станет ясно чуть ниже, когда дойдём до proc-macro2. Пока достаточно знать, что rustc отдаёт макросу токены вашего кода в виде структуры TokenStream. Внутри serde_derive код достаёт из TokenStream нужное: имя структуры, поля, атрибуты, дженерики.
По ним он генерирует реализацию Serialize и Deserialize и возвращает её обратно в rustc, снова в виде TokenStream. Компилятор подставляет полученный код на место #, как будто вы написали impl Serialize for руками, и продолжает компилировать крейт уже с этой вставкой. Здесь часто возникает путаница.
proc-macro это не какая-то специальная функция компилятора. Это полноценная программа, которую собирают отдельно от вашего проекта и подгружают в процесс rustc. У неё есть всё, что есть у обычного Rust-кода: открыть файл, сходить в сеть, прочитать переменные окружения, упасть с паникой.
Событие, по словам экспертов, усилит конкуренцию в сфере ИИ.





