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. A Sync type is also Send.

The classifications form a strict ordering: Unsend < Send < Sync.

Built-in Facts

The following types are intrinsically Sync:

  • All integer types (i8i64, u8u64, 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 SyncSync 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 to Unsend. Always permitted; the marker only restricts.
  • checked_send — declares the type Send. The compiler does NOT verify the claim; the user takes responsibility for the assertion (analogous to Rust's unsafe impl Send). The checked_ prefix flags the marker as user-asserted.
  • checked_sync — declares the type Sync. Same caveat as checked_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).