Some traits describe properties without declaring methods of their own.
Copy, Sized, Send and Sync are important examples, but they answer
different questions:
| Trait | What it tells you |
|---|---|
Copy |
Assigning or passing the value can implicitly duplicate it. |
Sized |
The type has a size known at compile time. |
Send |
Ownership of the value can safely move to another thread. |
Sync |
Shared references to the value can safely cross thread boundaries. |
A trait with no methods of its own is often called a marker trait.
Send and Sync are also auto traits: the compiler normally determines
them from a type's components. Copy is not automatic for your structs;
you opt in when their fields permit it. Sized is determined by the compiler.
Sized: allow different values, not different layouts
An ordinary generic type parameter has an implicit Sized bound.
fn f<T>(value: T) therefore requires T: Sized. This does not mean the
compiler knows the value, only its type's size.
For example, [u8; 16] has a fixed size. The slice type [u8] and the string
slice type str do not encode a length in their type. Trait object types such
as dyn std::fmt::Debug are also unsized. References to these types are sized.
Use ?Sized to allow a parameter to be either sized or unsized:
fn byte_size<T: ?Sized>(value: &T) -> usize {
std::mem::size_of_val(value)
}
assert_eq!(byte_size("hello"), 5); // T is str
assert_eq!(byte_size(&[1_u8, 2, 3]), 3); // T is [u8; 3]
?Sized removes a restriction; it does not require an unsized type. A generic
function cannot take an arbitrary unsized T by value on stable Rust, so the
example accesses it through &T. Unsized data does not necessarily live on
the heap: a slice can borrow part of a stack-allocated array.
The implicit bound also has an important exception: a trait's Self type is
not Sized by default. A trait can explicitly require Self: Sized when
needed.
References to slices carry length information; references to trait objects
carry information used for dynamic dispatch. These are commonly called wide
pointers. On common 64-bit targets, &str occupies two machine words, but
do not turn that observation into a layout guarantee for every unsized type.
?Sized is a type bound, not a specification of pointer representation.
Copy: implicit duplication has restrictions
#[derive(Clone, Copy)]
struct Point {
x: i32,
y: i32,
}
let first = Point { x: 2, y: 3 };
let second = first;
assert_eq!(first.x + second.y, 5); // first is still usable
For a struct to implement Copy, every field must be Copy, and the struct
must not implement Drop. String and Vec<T> are not Copy; duplicating
their ownership information would not create an independent allocation.
Shared references are Copy even when the referenced value is not. Mutable
references are not Copy.
Copy requires Clone as a supertrait. Consequently, T: Copy also makes
Clone::clone available, despite Copy declaring no methods itself. The
implication goes one way: String implements Clone without implementing
Copy. Deriving both is convenient, but manual implementations are possible.
Copying does not call clone(). For a Copy type, a manual clone should
return *self; Clippy's non_canonical_clone_impl checks this convention.
The semantic difference between copying and moving is whether the original
remains usable, not a promise that either operation emits a machine-level
memory copy. Optimization can remove that work.
Send and Sync: ownership and sharing are separate
T: Send permits transferring ownership to another thread. T: Sync means
&T: Send, permitting shared access across threads. Neither bound alone
implies 'static; thread APIs may impose additional lifetime requirements.
For ordinary structs without an explicit implementation, the compiler checks their fields. A field that lacks an auto trait prevents that trait from being supplied automatically. Library types can have more specific implementations and safety requirements:
| Type | Send when |
Sync when |
|---|---|---|
Rc<T> |
Never | Never |
Arc<T> |
T: Send + Sync |
T: Send + Sync |
RefCell<T> |
T: Send |
Never |
Mutex<T> |
T: Send |
T: Send |
*const T or *mut T |
Never | Never |
Rc uses non-atomic reference counting. Arc uses atomic reference counting,
but that protects the count, not arbitrary operations on the value inside.
For example, Arc<RefCell<u32>> is neither Send nor Sync.
Mutex<RefCell<u32>> can be both: access to the inner value goes through the
lock. Choose a wrapper whose guarantees match how the data will be accessed.
Check a bound at compile time
An empty helper can check a positive trait bound without running any work:
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
assert_send::<u32>();
assert_sync::<std::sync::Mutex<std::cell::RefCell<u32>>>();
Replacing u32 with std::rc::Rc<u32> makes the first call fail to compile.
Keep such intentionally failing examples separate from code expected to run.
Projects can use compile-fail tests to verify a rejected bound; an ordinary
runtime assertion does not test compiler rejection. This lesson is an article
so you can examine both successful and rejected examples.
Manual auto-trait implementations need a safety argument
An unsafe impl Send or unsafe impl Sync is a promise that safe callers can
rely on. It can be appropriate for a wrapper around a raw pointer when its
ownership, access and destruction rules establish the required guarantee.
A lock is not always the answer: exclusive ownership may justify transferring
a value, while sharing references requires its own argument.
Do not add an unsafe implementation merely to silence a compiler error. Review every operation the type exposes, including what happens when it is dropped on another thread.
Explicit negative implementations such as impl !Send for LocalHandle {}
remain unstable. On stable Rust, a marker field can prevent automatic Send
and Sync implementations:
struct LocalHandle {
id: u32,
_local: std::marker::PhantomData<*const ()>,
}
The marker occupies no storage and owns no actual pointer. It still affects the enclosing type's auto traits. Use this deliberately when the type must remain on one thread, and document the reason.