๐Ÿฆ€ Functional Rust
๐ŸŽฌ Traits & Generics Shared behaviour, static vs dynamic dispatch, zero-cost polymorphism.
๐Ÿ“ Text version (for readers / accessibility)

โ€ข Traits define shared behaviour โ€” like interfaces but with default implementations

โ€ข Generics with trait bounds: fn process(item: T) โ€” monomorphized at compile time

โ€ข Static dispatch (impl Trait) = zero cost; dynamic dispatch (dyn Trait) = runtime flexibility via vtable

โ€ข Blanket implementations apply traits to all types matching a bound

โ€ข Associated types and supertraits enable complex type relationships

398: Auto Traits and Negative Impls

Difficulty: 4 Level: Expert Traits the compiler implements automatically based on a type's fields โ€” and how to override that decision.

The Problem This Solves

`Send` and `Sync` need to propagate through composed types. If `Arc<T>` is `Send`, that should only be true when `T: Send`. If a struct contains an `Rc<i32>`, the struct should automatically be `!Send`. Writing all these implications by hand would be impossible โ€” there are infinite combinations of types. Auto traits solve this with a simple rule: a type automatically implements an auto trait if and only if all its fields do. The compiler computes this bottom-up: primitives have known properties, composed types inherit them. No user code required. The flip side is that `unsafe` code sometimes manages its own synchronization. A type with raw pointers is `!Send` by default (raw pointers are not auto-`Send`), even if the implementation is actually thread-safe. Negative impls and unsafe positive impls let you override the compiler's conservative default.

The Intuition

Think of auto traits as properties that propagate structurally: a container is thread-safe if and only if its contents are thread-safe. The compiler does this analysis automatically โ€” you never write `impl Send for Vec<T> where T: Send`; it just works. Negative impls (`impl !Send for Rc<T>`) let a type explicitly opt out of an auto trait, even if the structural rule would have allowed it. `Rc<T>` has a `*mut u8` internally โ€” raw pointers are `!Send` by default, so `Rc` would already be `!Send`. The explicit `!Send` impl is documentation that reinforces the intent. `unsafe impl Send` goes the other direction: "the structural rule says I'm `!Send`, but I've manually verified my synchronization is correct, and I'm taking responsibility."

How It Works in Rust

use std::cell::Cell;
use std::sync::Arc;
use std::rc::Rc;

fn require_send<T: Send>(_: T) {}
fn require_sync<T: Sync>(_: &T) {}

// These compile โ€” primitives and Arc are Send + Sync:
require_send(42i32);
require_sync(&42i32);
let arc = Arc::new(42i32);
require_send(arc.clone());  // Arc<i32>: Send because i32: Send

// This would FAIL to compile โ€” Rc is !Send:
// let rc = Rc::new(42i32);
// require_send(rc);  // ERROR: Rc<i32> cannot be sent between threads safely

// Propagation: struct is Send iff ALL fields are Send
struct AllSend { x: i32, y: String, z: Arc<f64> }
// AllSend: automatically Send (all fields are Send)

// This struct would be !Send because Rc is !Send:
// struct HasRc { x: i32, rc: Rc<i32> }

// Cell<T> is !Sync (allows interior mutability without locking)
struct NonSyncType {
 data: Cell<i32>,  // Cell<i32>: !Sync
}
// NonSyncType is automatically !Sync

// Unsafe override: raw pointer makes MyBuffer !Send by default
// but we know our usage is thread-safe
struct MyRawBuffer {
 ptr: *mut u8,
 len: usize,
}
unsafe impl Send for MyRawBuffer {}
unsafe impl Sync for MyRawBuffer {}
The three cases:
CaseWhat you writeWhen to use
Auto (positive)NothingType's fields are all Send/Sync โ€” just works
Unsafe positive`unsafe impl Send for T {}`Fields aren't Send (raw pointers) but logic is thread-safe
Negative`impl !Send for T {}` (nightly)Opt out despite structural rule allowing it

What This Unlocks

Key Differences

ConceptOCamlRust
Thread safety propagationManual โ€” developer ensures correctnessAuto traits propagate structurally; compiler enforces
Override mechanismN/A (no auto traits)`unsafe impl` (positive override), `impl !Trait` (negative, nightly)
Cell/Rc safetyGC manages memory; OCaml 5 domains have separate heaps`Cell`โ†’`!Sync`, `Rc`โ†’`!Send` โ€” type system enforces at compile time
CostRuntime GC handles sharingZero cost โ€” all decisions at compile time
// Auto traits and negative impls in Rust
// Note: negative impls require nightly for custom types.
// This example shows the concept and std library behavior.

use std::cell::Cell;
use std::sync::Arc;
use std::rc::Rc;

// Check Send/Sync at compile time
fn require_send<T: Send>(_: T) {}
fn require_sync<T: Sync>(_: &T) {}

// Custom type: unsafe impl to assert thread safety
struct MyRawBuffer {
    // Contains a raw pointer โ€” not automatically Send
    ptr: *mut u8,
    len: usize,
}

// We know this is safe because we manage access properly
unsafe impl Send for MyRawBuffer {}
unsafe impl Sync for MyRawBuffer {}

// A type that should NOT be Send (contains Cell)
struct NonSendable {
    // Cell<T> is !Sync because it allows interior mutability without locks
    data: Cell<i32>,
}
// NonSendable is automatically !Sync because Cell<i32> is !Sync
// If we tried: require_sync(&NonSendable { data: Cell::new(0) }), it would fail.

fn demonstrate_auto_traits() {
    // i32: Send + Sync (automatically)
    require_send(42i32);
    require_sync(&42i32);

    // Arc<i32>: Send + Sync (thread-safe reference counting)
    let arc = Arc::new(42i32);
    require_send(arc.clone());
    require_sync(&arc);

    // Rc<i32>: !Send (non-atomic ref counting)
    // This would NOT compile:
    // let rc = Rc::new(42i32);
    // require_send(rc); // ERROR: Rc<i32> cannot be sent between threads safely

    println!("Auto trait checks passed!");
}

fn demonstrate_propagation() {
    // A struct is Send iff all fields are Send
    struct AllSend { x: i32, y: String, z: Arc<f64> }
    // AllSend is automatically Send

    struct HasRc { x: i32, rc: Rc<i32> }
    // HasRc is automatically !Send because Rc<i32> is !Send

    require_send(AllSend { x: 1, y: "hi".to_string(), z: Arc::new(1.0) });
    // require_send(HasRc { x: 1, rc: Rc::new(1) }); // Would fail!
    println!("Propagation: struct is Send iff all fields are Send");
}

fn main() {
    demonstrate_auto_traits();
    demonstrate_propagation();

    // MyRawBuffer is Send due to unsafe impl
    let buf = MyRawBuffer { ptr: std::ptr::null_mut(), len: 0 };
    require_send(buf);
    println!("MyRawBuffer: unsafe impl Send works");
}

#[cfg(test)]
mod tests {
    use super::*;

    fn assert_send<T: Send>() {}
    fn assert_sync<T: Sync>() {}
    fn assert_not_send<T>() where T: ?Sized {} // just to document

    #[test]
    fn test_send_types() {
        assert_send::<i32>();
        assert_send::<String>();
        assert_send::<Arc<i32>>();
        assert_send::<MyRawBuffer>();
    }

    #[test]
    fn test_sync_types() {
        assert_sync::<i32>();
        assert_sync::<Arc<i32>>();
    }
}
(* Auto traits concept in OCaml โ€” thread safety via domains *)

(* OCaml 5 has domains for parallelism; auto trait concept via polymorphic types *)

(* Simulate the concept: a "non-shareable" wrapper *)
type 'a single_threaded = { value: 'a; thread_id: int }

let create v = { value = v; thread_id = Thread.id (Thread.self ()) }

(* Only same thread can access *)
let get st =
  assert (Thread.id (Thread.self ()) = st.thread_id);
  st.value

(* A shareable wrapper โ€” can be used from any thread *)
type 'a shared = {
  value: 'a;
  mutex: Mutex.t;
}

let create_shared v = { value = v; mutex = Mutex.create () }

let () =
  Printf.printf "Auto traits simulate thread safety constraints\n";
  Printf.printf "In OCaml, domain-safety is managed differently\n"