Auto Traits
An auto trait (also called an OIBIT — Opt-In Built-In Trait) is a marker trait that the compiler automatically implements for every struct, enum, union, tuple, and closure unless at least one of its constituent fields (or an explicit negative implementation impl !AutoTrait for Type) opts out.
Built-In Auto Traits in the Standard Library
While declaring new custom auto trait items requires nightly Rust (#![feature(auto_traits)]), five foundational auto traits in the standard library govern concurrency, pinning, and panic unwinding on stable Rust:
| Auto Trait | Module | Meaning | Types That Opt Out (!Trait) |
|---|---|---|---|
Send |
std::marker |
Safe to transfer ownership across thread boundaries | *const T, *mut T, Rc<T>, MutexGuard<'_, T> |
Sync |
std::marker |
Safe to share references (&T) across threads concurrently |
*const T, *mut T, Rc<T>, UnsafeCell<T>, Cell<T>, RefCell<T> |
Unpin |
std::marker |
Safe to move out of a Pin<&mut T> pointer |
PhantomPinned, compiler-generated async block futures |
UnwindSafe |
std::panic |
Safe to access after catching a panic via catch_unwind without observing broken invariants |
&mut T, &RefCell<T>, &Mutex<T>, UnsafeCell<T> |
RefUnwindSafe |
std::panic |
Safe to access through a shared reference &T across catch_unwind |
UnsafeCell<T>, Cell<T>, RefCell<T> |
Structural Propagation Algorithm
The compiler determines whether a composite type Composite implements an auto trait A using a structural coinductive rule:
Does `struct Composite { f1: T1, f2: T2 }` implement Auto Trait `A`?
|
+-- 1. Is there an explicit `impl A for Composite` or `impl !A for Composite`?
| Yes -> Use the explicit implementation!
|
+-- 2. Otherwise, check every field `T1, T2, ...`:
+-- All fields implement `A` ---> `Composite` implements `A` automatically!
+-- Any field is `!A` ---> `Composite` does NOT implement `A`!
Notice that recursive types (struct Node { next: Option<Box<Node>> }) are handled coinductively: if assuming Node: Send makes all fields of Node satisfy Send, then Node implements Send.
Inspecting Auto-Trait Propagation and Opting Out via PhantomData
Because defining impl !Send for MyType {} directly requires nightly #![feature(negative_impls)], stable Rust code opts a struct out of Send and Sync by including a zero-sized PhantomData<*const ()> field (since raw pointers are !Send and !Sync), or opts out of Sync only (while remaining Send) using PhantomData<Cell<()>>:
use std::cell::Cell;
use std::marker::PhantomData;
// 1. Default struct with ordinary fields: automatically `Send + Sync + Unpin`
struct NormalData {
id: u64,
}
// 2. Opt out of `Sync` (remains `Send`):
struct SendButNotSync {
id: u64,
_not_sync: PhantomData<Cell<()>>,
}
// 3. Opt out of BOTH `Send` and `Sync`:
struct ThreadLocalOnly {
id: u64,
_not_send_or_sync: PhantomData<*const ()>,
}
const fn assert_send<T: Send>() {}
const fn assert_sync<T: Sync>() {}
const _: () = {
assert_send::<NormalData>();
assert_sync::<NormalData>();
assert_send::<SendButNotSync>();
};
fn main() {
let a = NormalData { id: 1 };
let b = SendButNotSync {
id: 2,
_not_sync: PhantomData,
};
let c = ThreadLocalOnly {
id: 3,
_not_send_or_sync: PhantomData,
};
println!("ids: {}, {}, {} (size of each = {} B)", a.id, b.id, c.id, size_of::<ThreadLocalOnly>());
}
ids: 1, 2, 3 (size of each = 8 B)