Send и Sync — это marker traits (трейты-маркеры), которые управляют безопасностью многопоточности: Send разрешает перемещение значения между потоками, а Sync разрешает деление ссылки на значение между потоками (то есть &T должен быть Send). Эти трейты автоматически выводятся компилятором для всех типов, но могут быть явно указаны через unsafe impl, когда разработчик гарантирует, что тип не содержит data race — Send требует полного владения или атомарных операций, а Sync требует синхронизированного доступа к внутреннему состоянию.
Send — тип можно безопасно перемещать между потоками.
Sync — ссылку на тип можно безопасно делить между потоками.
Send = Sent to another thread (перемещение ownership) Sync = Shared across threads (разделение ссылки)
| Тип | Send | Sync | Примечание |
| (), u8, i32, f64, bool, char | ✅ | ✅ | Примитивы без внутреннего состояния |
| &T (неизменяемая ссылка) | ❌ | ✅ | &T: Send только если T: Sync |
| &mut T (изменяемая ссылка) | ✅ | ❌ | Владеет эксклюзивным доступом |
| Box<T> | T: Send | T: Sync | Владение кучей |
| Vec<T>, String, HashMap<T, U> | T: Send | T: Sync | Владение коллекцией |
| Option<T>, Result<T, E> | T: Send, E: Send | T: Sync, E: Sync | Контейнеры |
| fn() (function pointer) | ✅ | ✅ | Без состояния |
| Closure | ✅ | ❌ | Если не захватывает !Sync |
| Arc<T> | T: Send | T: Sync | Атомарный счётчик |
| Rc<T> | ❌ | ❌ | Неатомарный счётчик |
| RefCell<T> | T: Send | ❌ | Внутренняя мутабельность |
| Mutex<T> | T: Send | T: Sync | Синхронизирует доступ |
| AtomicBool, AtomicUsize и т.д. | ✅ | ✅ | Атомарные операции |
| UnsafeCell<T> | T: Send | ❌ | Ядро внутренней мутабельности |
Правила производных типов
- Кортежи и структуры
(T1, T2, ..., Tn): Send ⇔ T1: Send && T2: Send && ... && Tn: Send
(T1, T2, ..., Tn): Sync ⇔ T1: Sync && T2: Sync && ... && Tn: Sync
- Умные указатели
Box<T>: Send ⇔ T: Send
Box<T>: Sync ⇔ T: Sync
Rc<T>: Send ⇔ ❌ (всегда false)
Rc<T>: Sync ⇔ ❌ (всегда false)
Arc<T>: Send ⇔ T: Send
Arc<T>: Sync ⇔ T: Sync
- Контейнеры
Vec<T>: Send ⇔ T: Send
Vec<T>: Sync ⇔ T: Sync + Clone (для некоторых операций)
String: Send ⇔ ✅ (всегда true)
String: Sync ⇔ ✅ (всегда true)
- Функции и замыкания
extern "C" fn(): Send ⇔ ✅
extern "C" fn(): Sync ⇔ ✅
FnMut closure: Send ⇔ захваченные переменные: Send
FnOnce closure: Send ⇔ захваченные переменные: Send
Автотрейты (compiler-generated)
Rust автоматически реализует трейты Send/Sync. Не нужно писать имплементацию этих трейтов в большинстве случаев. Если программист все же вынужден это делать, то он ступает на дорогу Unsafe, где ответственность за безопасность данных ложится на него.
Sync и Send — автоматические и unsafe трейты-маркеры. Это значит, что:
- У них нет методов и ассоциированных типов.
- Их компилятор реализует для всех подходящих типов автоматически — не нужно писать
implи#[derive(..)]. - Реализовать их самостоятельно — небезопасно. Можно, но тогда механизмы безопасности Rust не подстрахуют, потому что Send и Sync сами являются фундаментом этой безопасности.
Send и Sync реализуются компилятором автоматически, когда он видит, что тип обеспечивает такие гарантии. Если написать структуру, одно из полей которой не будет реализовывать Send и/или Sync, то и вся структура не будет реализовывать Send и/или Sync соответственно.
#[derive(Debug)]
struct MyStruct {
field1: i32, // Send ✅, Sync ✅
field2: String, // Send ✅, Sync ✅
}
// Результат:
// MyStruct: Send ✅ (все поля Send)
// MyStruct: Sync ✅ (все поля Sync + Copy для field1)
Когда нужно unsafe impl
// Пример: структура с внешним ресурсом
struct MyFd {
fd: RawFd, // не-OWNING raw pointer
}
// Rust не может автоматически определить Send/Sync для raw pointers
// Поэтому нужно явно указать:
unsafe impl Send for MyFd {} // если fd не используется в нескольких потоках
unsafe impl Sync for MyFd {} // если доступ к fd синхронизирован внешними примитивами
Проверка в многопоточном коде:
use std::thread;
fn main() {
let data = String::from("hello");
thread::spawn(move || {
println!("{}", data);
}); // ✅ String: Send
let data_rc = Rc::new(String::from("hello"));
thread::spawn({
let data_rc = data_rc.clone();
move || {
println!("{}", data_rc);
}
}); // ❌ Rc<String>: !Send
}
Типичные паттерны
Безопасные многопоточные структуры
use std::sync::{Arc, Mutex};
// Паттерн 1: Arc<Mutex<T>> - shared mutable state
let shared = Arc::new(Mutex::new(0));
let shared_clone = Arc::clone(&shared);
thread::spawn(move || {
let mut num = shared_clone.lock().unwrap();
*num += 1;
});
// Паттерн 2: Arc<Atomic*> - lock-free synchronization
use std::sync::atomic::{AtomicUsize, Ordering};
let counter = Arc::new(AtomicUsize::new(0));
let counter_clone = Arc::clone(&counter);
thread::spawn(move || {
counter_clone.fetch_add(1, Ordering::SeqCst);
});
Небезопасные многопоточные структуры
use std::cell::RefCell;
use std::rc::Rc;
// Плохой пример:
let bad = Rc::new(RefCell::new(0));
thread::spawn({
let bad_clone = bad.clone();
move || {
*bad_clone.borrow_mut() = 1; // ❌ RefCell: !Sync, Rc: !Send
}
}); // compilation error!
Памятка: «Правило 3-х вопросов»
При определении Send/Sync для своего типа задайте:
1. Ownership может быть перемещён в другой поток?
- Если нет → не Send
- Если да → проверяйте Sync
2. Ссылка &T может быть разделена между потоками?
- Если нет → не Sync
- Если да → &T: Send
3. Есть ли внутреннее состояние, которое может вызвать data race?
- Атомарные операции → безопасно
- Неатомарные операции → требует синхронизации
- Raw pointers → unsafe
Частые причины ошибок
- Rc<T> doesn’t implement Send — Неатомарный счётчик — Использовать Arc<T>
- RefCell<T> doesn’t implement Sync — Внутренняя мутабельность без синхронизации — Использовать Mutex<T> или RwLock<T>
- *mut <T> doesn’t implement Send — Raw pointer — Обернуть в безопасный тип с unsafe impl
- Closure doesn’t implement Send — Захватывает !Send переменную — Использовать Arc<Mutex<T>> для захвата
Ключевые типы для многопоточности
// Для shared mutable state (один writer или many readers):
Arc<Mutex<T>> // блокировки
Arc<RwLock<T>> // множественные читатели, один writer
// Для lock-free atomic operations:
Arc<AtomicUsize> // атомарные операции без блокировок
Arc<AtomicBool>
AtomicPtr<T>
// Для ownership в одном потоке:
Rc<T> // не для многопоточности!
RefCell<T> // не для многопоточности!
Cell<T> // не для многопоточности!
Запомните: Send и Sync — это compiler-enforced guarantees о безопасности многопоточности. Если код компилируется с thread::spawn, то он гарантированно free от data race! 🎉
Дополнительные ресурсы
Rust Reference: Send trait
Rust Reference: Sync trait
Rustonomicon: Unsafe Code Guidelines
Rust Reference: Module Atomic
Rust By Example: Shared State

