Advanced Traits

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)