Ограничение Sync, о котором никто не просил
- title
- Ограничение Sync, о котором никто не просил
- type
- summary
- summary
- Разбор verrchu: почему
&selfв асинхронных трейтах сSend-футурами неявно требуетSelf: Syncи почему&mut selfрешает проблему - parent
- rust-send-sync
- tags
- rust, async, type-system
- sources
- verrchu-sync-bound
- created
- 2026-05-10
- updated
- 2026-05-10
- lang
- ru
- translation_of
- rust-async-trait-sync-bound
- source_updated
- 2026-05-10
- translated
- 2026-09-01
- translator
- lllm/antigravity/gemini-3.7-flash-medium
Первый пост в блоге verrchu разбирает ошибку в асинхронных трейтах Rust, которая выглядит как проблема с Send, но на деле маскирует проблему с Sync. В основе разбора лежит таблица истинности из четырёх ячеек:
impl Send + Sync |
impl Send + !Sync |
|
|---|---|---|
футура без ограничения + Send |
компилируется | компилируется |
футура с ограничением + Send |
компилируется | ошибка |
Падающий вариант (случай 2.2) не компилируется всего из-за двух неочевидных правил Rust:
tokio::spawnтребуетF: Send + 'static, поэтому возвращаемая футура обязана бытьSend&T: SendтребуетT: Sync(тогда как&mut T: Sendтребует толькоT: Send)
Поэтому если в трейте объявлен метод fn run(&self) -> impl Future<Output = ()> + Send, захват &self внутри футуры означает, что футура захватывает ссылку &Self. Чтобы этот захват был Send, тип Self обязан реализовывать Sync. Требование Sync всё это время скрывалось внутри &self - стороннее поле с !Sync (Cell<()>) просто делает его явным.
Два способа исправления:
- Обернуть поле
!SyncвMutex- работает, потому что дляMutex<T>: Syncтребуется толькоT: Send, но добавляет накладные расходы на блокировку во время выполнения, хотяSelfна самом деле никогда не разделяется между потоками - Заменить
&selfна&mut self- для&mut T: Sendтребуется толькоT: Send, поэтому требованиеSyncполностью исчезает
Второе решение контринтуитивно. В Rust первая интуиция - взять &, потому что это ограничение кажется "строже" (нет мутации). Но с точки зрения трейт-баундов оно оказывается слабее. Настоящий смысл &mut T - уникальная ссылка, а не мутабельная ссылка. Именно уникальность исключает одновременное разделение между потоками, что и снимает требование Sync. (Статья Нико Мацакиса "Focusing on ownership" - канонический текст об &mut как об уникальности.)
Пост короткий, наглядно проиллюстрирован полными текстами ошибок компилятора и служит хорошей отправной точкой для более широкой темы о том, как Send/Sync каскадно влияют на типы возвращаемых значений в асинхронных трейтах. Практический вывод: если метод асинхронного трейта должен возвращать Send-футуру, по умолчанию используйте &mut self, если вам явно не нужен разделяемый доступ.
См. также: rust-send-sync, verrchu-blog, message-passing-vs-shared-memory.