
Твой async fn на самом деле enum, а Pin нужен потому, что Rust наступил на грабли самоссылающихся структур
vibecodingai 33 минуты назад Твой async fn на самом деле enum, а Pin нужен потому, что Rust наступил на грабли самоссылающихся структур Сложный 7 мин 1.6K Rust * Системное программирование * Параллельное...
Anthropic — What company has the best second artificial intelligence model at the end of June?
В сфере искусственного интеллекта произошло заметное событие. vibecodingai 33 минуты назад Твой async fn на самом деле enum, а Pin нужен потому, что Rust наступил на грабли самоссылающихся структур Сложный 7 мин 1. 6K Rust * Системное программирование * Параллельное программирование * Мнение TL;DR. Каждый async fn в Rust компилируется в enum-стейт-машину.
Размер этой стейт-машины равен размеру самого толстого варианта, поэтому забытая через . await переменная на пару мегабайт превращается в утечку памяти, помноженную на число задач. Pin существует, чтобы запретить перемещать такие стейт-машины после первого poll, потому что внутри них живут указатели на собственные поля.
Технические детали
молча теряет данные, если использовать в нём future без cancellation safety. И executor в Tokio, при всей его магии, концептуально умещается в сотню строк. Содержаниеasync fn это синтаксический сахар над enumРазмер future и почему твой сервис ест памятьСамоссылающиеся структуры и зачем PinWaker, или как future вообще узнаёт, что пора просыпатьсяCancellation safety, или почему select!
иногда теряет данныеСвой executor за двести строкБонус: async fn в трейтахЧто с этим делать сегодняПредставь сервис на Tokio, который держит десять тысяч соединений и неожиданно начинает съедать восемь гигабайт памяти на ровном месте. В коде нет утечек, Arc всё считает корректно, valgrind молчит. Виновник — один безобидный async fn, в котором между двумя .
await лежит массив на пару мегабайт. Компилятор честно положил его в стейт-машину, и теперь каждое из десяти тысяч соединений таскает за собой эту память. Чтобы такие истории перестали быть мистикой, надо перестать думать об async Rust как о чёрном ящике и разобрать три темы, которые в обычной жизни не встречаются: трансформацию async fn в стейт-машину, самоссылающиеся структуры и зачем Pin, и как на самом деле работает executor.
Отраслевые последствия
Под капотом async Rust устроен неожиданно просто и неожиданно жестоко: каждый твой async fn это сгенерированный компилятором enum, каждый . await это match по этому enum, а легендарный Pin, на который ругается половина туториалов, существует ровно потому, что без него вся эта конструкция разваливается на первом же mem::swap. В конце соберём свой собственный executor за двести строк, чтобы окончательно стало ясно, что Tokio это не магия.
async fn это синтаксический сахар над enumВозьмём абсолютно ничего не делающую функцию:async fn foo() -> Если поставить cargo install cargo-expand и запустить cargo expand, можно увидеть реальный enum, в который компилятор разворачивает эту функцию. В упрощённом виде он выглядит так:enum Каждый . await это точка приостановки, и для каждой такой точки в enum появляется свой вариант с локальными переменными, которые должны пережить приостановку.
poll у этого Future это огромный match, который смотрит текущее состояние, вызывает вложенный future, и если тот вернул Pending, сохраняет состояние и возвращает Pending наверх. Если Ready — переходит в следующий вариант enum.
Этот прогресс даёт важные сигналы о будущем отрасли, и технологический мир внимательно наблюдает.





