title: “NonNull Send, Sync, and Variance in Rust” meta_description: “Understanding how NonNull affects Send, Sync, variance, and covariance in Rust’s type system.” keywords: [“rust variance”, “covariance”, “send sync nonnull”, “type system rust”]
🔄 Reinventing from Scratch: NonNull<T>
Chapter 8 — “Send, Sync, Variance, and Covariance”
“NonNull is covariant in T — and that’s exactly what you want.”
🧭 8.1. Send and Sync
NonNull<T> implements Send and Sync based on T:
// If T: Send, then NonNull<T>: Send
// If T: Sync, then NonNull<T>: Sync
Example: Thread Safety
let ptr: NonNull<i32> = ...;
// i32: Send + Sync
// Therefore: NonNull<i32>: Send + Sync
std::thread::spawn(move || {
// Can move NonNull<i32> between threads
let ptr = ptr;
});
But:
use std::rc::Rc;
let ptr: NonNull<Rc<i32>> = ...;
// Rc is !Send !Sync
// Therefore: NonNull<Rc<i32>> is !Send !Sync
// std::thread::spawn(move || {
// let ptr = ptr; // ❌ Error: Rc is not Send!
// });
📊 8.2. Variance and Covariance
Variance = How subtyping relationships are preserved through generic types.
What is Covariance?
// String is a subtype of &str (can be coerced)
let s: String = "hello".into();
let s_ref: &str = &s;
// Box<T> is covariant:
let box_string: Box<String> = Box::new("hello".into());
let box_str: Box<&str> = box_string; // ✅ OK
NonNull is Covariant
// NonNull<T> is covariant in T
let nn_string: NonNull<String> = ...;
// Can treat as NonNull<&str>... in theory
// (In practice, you'd need transmute, but type system allows it)
Why this matters: Container types using NonNull inherit good variance properties!
🎯 8.3. Comparison with *mut T
// *mut T is INVARIANT
// Cannot coerce *mut String to *mut &str
// NonNull<T> is COVARIANT
// Can coerce NonNull<String> to NonNull<&str> (with transmute)
Result: Types using NonNull have better variance!
🧠 8.4. Chapter Takeaways
- Send/Sync inherited from
T - Covariant in
T - Better variance than
*mut T
Next: Chapter 9 — Patterns, Internals, and FFI 🌐