Thread Safety
This section describes how Gruel classifies types for thread-boundary crossings. The classification is a single trichotomy ordered as Unsend < Send < Sync.
Trichotomy
Every type MUST carry exactly one of three thread-safety classifications:
Unsend— values of this type MUST NOT cross a thread boundary.Send— values of this type MAY be moved across a thread boundary, transferring ownership.Sync— values of this type MAY be shared across thread boundaries by reference. ASynctype is alsoSend.
The classifications form a strict ordering: Unsend < Send < Sync.
Built-in Facts
The following types are intrinsically Sync:
- All integer types (
i8–i64,u8–u64,isize,usize). - All floating-point types (
f16,f32,f64). - The boolean type (
bool). - The character type (
char). - The unit type (
()). - The never type (
!).
The raw pointer types Ptr(T) and MutPtr(T) are intrinsically Unsend regardless of T. Containing types MAY override this classification with @mark(checked_send) or @mark(checked_sync) on the host struct/enum head.
Structural Inference
For composite types not covered by a built-in fact (named structs, named enums, anonymous structs/enums, arrays, tuples, references, slices, vectors), the thread-safety classification is the minimum over the classifications of all members (fields, variant payloads, elements, referents).
An empty composite (zero fields, zero variants, zero-length array) MAY be treated as Sync — Sync is the identity element for the structural-minimum operation.
struct Point { x: i32, y: i32 } // Sync (every field is Sync)
struct Handle { ptr: MutPtr(u8) } // Unsend (raw pointer poisons the min)
Markers
Three marker names recognized inside the @mark(...) directive (ADR-0083) override a type's structurally-inferred thread-safety:
unsend— downgrades the type toUnsend. Always permitted; the marker only restricts.checked_send— declares the typeSend. The compiler does NOT verify the claim; the user takes responsibility for the assertion (analogous to Rust'sunsafe impl Send). Thechecked_prefix flags the marker as user-asserted.checked_sync— declares the typeSync. Same caveat aschecked_send.
At most one thread-safety marker MAY be applied to any single type. Applying more than one of unsend, checked_send, checked_sync to the same declaration is a compile-time error.
@mark(checked_send) struct Job { id: i32, queue: MutPtr(u8) }
// Structurally Unsend (because of the MutPtr field), but the user
// asserts the type is Send-safe in practice.
Posture markers (copy / affine / linear) and thread-safety markers occupy independent namespaces; one of each MAY appear on the same type.
@mark(linear, checked_sync) struct LockedPool { /* ... */ }
Prelude container types (Vec(T), String, Option(T), Result(T, E), …) currently infer their thread-safety structurally — typically Unsend due to internal raw pointer fields. Container-specific overrides (e.g. Vec(T) being Sync when T is Sync) are added in follow-up changes that wrap the container body in comptime if over @thread_safety(T).