Negative Implementations and Their Limitations
A negative trait implementation (impl !AutoTrait for MyType {}) explicitly declares that a type does not implement an auto trait (such as Send, Sync, or Unpin), overriding the compiler's automatic structural deduction. It is distinct from a negative trait bound (where T: !Trait), which is not supported in Rust.
The Relaxed Bounds Rule: ?Sized Is the Only ?Trait Bound
Sometimes developers confuse negative implementations (!Trait) with the relaxed bound syntax ?Sized. In Rust, ?Sized means "may or may not be Sized", relaxing the implicit default T: Sized bound on generic type parameters. No other trait is implicitly added to every generic parameter, so writing ?Send, ?Sync, or ?Clone is rejected by the compiler:
// ❌ does not compile
fn accept_maybe_clone<T: ?Clone>(_val: T) {}
fn main() {}
error: `?Trait` is not permitted in supertraits or bounds; only `?Sized` is permitted
--> src/main.rs:2:26
|
2 | fn accept_maybe_clone<T: ?Clone>(_val: T) {}
| ^^^^^^
Negative Auto-Trait Implementations (impl !Send) vs. Stable PhantomData Idioms
Inside the standard library (and on nightly with #![feature(negative_impls)]), types like Rc<T>, Cell<T>, MutexGuard<'_, T>, and PhantomPinned use explicit negative auto-trait implementations:
Standard Library Internal Definition Stable Rust Equivalent in User Crates
------------------------------------ -------------------------------------
impl<T: ?Sized> !Send for Rc<T> {} <===> _not_send: PhantomData<*const ()>
impl<T: ?Sized> !Sync for Cell<T> {} <===> _not_sync: PhantomData<Cell<()>>
impl !Unpin for PhantomPinned {} <===> _not_unpin: PhantomPinned
On stable Rust, including the corresponding zero-sized PhantomData or PhantomPinned marker field achieves the exact same negative auto-trait behavior at zero memory cost:
use std::cell::Cell;
use std::marker::{PhantomData, PhantomPinned};
// Opts out of `Send`, `Sync`, and `Unpin` on stable Rust using zero-sized markers:
struct PinnedThreadLocalHandle {
fd: i32,
_not_send_sync: PhantomData<*const ()>,
_not_unpin: PhantomPinned,
}
// Opts out of `Sync` only (stays `Send` and `Unpin`):
struct MovableUnsyncBuffer {
data: Vec<u8>,
_not_sync: PhantomData<Cell<()>>,
}
fn main() {
let h = PinnedThreadLocalHandle {
fd: 3,
_not_send_sync: PhantomData,
_not_unpin: PhantomPinned,
};
let b = MovableUnsyncBuffer {
data: vec![1, 2, 3],
_not_sync: PhantomData,
};
println!("handle fd = {}, size = {} bytes", h.fd, size_of_val(&h));
println!("buffer len = {}", b.data.len());
}
handle fd = 3, size = 4 bytes
buffer len = 3
Why Negative Trait Bounds (where T: !Copy) Are Excluded from Rust
If Rust allowed functions or blanket implementations to require that a type does not implement an ordinary trait (where T: !Clone or where T: !Display), then adding a new trait implementation (#[derive(Clone)] or impl Display for MyType) in an upstream library would become a major semver-breaking change, because downstream code relying on MyType: !Clone would suddenly stop compiling!
To keep trait additions backwards-compatible across the Cargo ecosystem, trait bounds in Rust are strictly monotonic (positive): implementing more traits for a type can only unlock more capabilities, never disable existing ones.