thread
Native OS threads: pthreads on POSIX, the Win32 thread API on Windows.
public module thread::local;
Everything except ThreadLocal lives at the root of the module — import std::thread; brings it in.
| File | What it holds | Import |
|---|---|---|
local | ThreadLocal<T>, lazy per-thread storage | import std::thread::local; |
Spawning
function spawn<C, T>(ctx: C, body: (C) -> T) -> JoinHandle<T> where C: Send, T: Send;
function spawn<C, T>(ctx: C, body: (C) -> T) -> JoinHandle<T> where C: Send, T: Send;Spawn a thread that runs body(ctx) and returns a handle to its T result. ctx is moved into the new thread. body must be a function pointer or non-capturing lambda. Panics on allocation or pthread_create failure; use try_spawn for a fallible spawn.
function try_spawn<C, T>(ctx: C, body: (C) -> T) -> Result<JoinHandle<T>, SpawnError> where C: Send, T: Send;
function try_spawn<C, T>(ctx: C, body: (C) -> T) -> Result<JoinHandle<T>, SpawnError> where C: Send, T: Send;Fallible spawn: like spawn, but returns Err(SpawnError) instead of panicking when the OS cannot create the thread (e.g. EAGAIN under a thread-count limit). The body's T is still delivered through the returned handle's join().
function spawn_with_attr<C, T>(ctx: C, body: (C) -> T, stack_bytes: u64) -> Result<JoinHandle<T>, SpawnError> where C: Send, T: Send;
function spawn_with_attr<C, T>(ctx: C, body: (C) -> T, stack_bytes: u64) -> Result<JoinHandle<T>, SpawnError> where C: Send, T: Send;Fallible spawn with an optional stack size (0 == OS default). The per-OS thread creator (os_create_thread) applies it - via a pthread_attr_t on POSIX, or CreateThread's dwStackSize on Windows.
spawn(ctx, body) moves ctx into a new thread and hands it to body, a (C) -> T function — a named function or a non-capturing lambda. The returned JoinHandle<T> hands the body's result back through join().
import std::thread;
const h = thread::spawn::<i32, i32>(21, (n: i32) -> i32 { return n * 2; });
const r: i32 = h.join(); // 42
Owned data — String, Box, Array — moves across the boundary in either direction: into the thread through ctx, and back out through the returned T. The move-checker tracks the transfer, so there is no aliasing and no double free.
The entry points require C: Send, T: Send, so moving a non-Send payload — an Rc, a lock guard — into another thread is a compile error.
try_spawn is the fallible variant, returning Result<JoinHandle<T>, SpawnError> instead of panicking when the OS refuses to create the thread. spawn_with_attr additionally sets the stack size; Builder is the friendlier spelling of the same thing.
Why an explicit context instead of a capturing closure? A capturing closure passed to a generic
(C) -> Tparameter does not compile yet. Passing the state explicitly throughctxis the design that works today; once the closure-capture carve-out lands, a capturingspawnbecomes sugar over this.
JoinHandle<T>
type struct JoinHandle<T> {
tid: u64;
: Shared<T>*;
join(mut this) -> T;
detach(mut this) -> void;
is_finished(&this) -> boolean;
id(&this) -> ThreadId;
}
| Method | Notes |
|---|---|
join() | Waits and returns the body's T. Consumes the handle. |
detach() | Fire and forget. Consumes the handle. |
is_finished() | Polls without consuming the handle. |
id() | ThreadId |
A handle must be joined or detached. Dropping one without either is equivalent to detach().
SpawnError
type enum SpawnError {
OutOfMemory;
PermissionDenied;
ResourceLimit;
Other;
}
implement enum SpawnError {
static from_errno(rc: i32) -> SpawnError;
}
Why the OS refused to create a thread, from try_spawn; from_errno is the mapping from the pthread return code.
Trait implementations
implement<T> trait Drop for struct JoinHandle<T>
Builder
type struct Builder {
stack_bytes: u64;
name_buf: [u8;
16];
name_len: u64;
static new() -> Builder;
stack_size(mut this, bytes: u64) -> Builder;
name(mut this, n: Str) -> Builder;
spawn<C, T>(this, ctx: C, body: (C) -> T) -> JoinHandle<T>
where C: Send, T: Send;
try_spawn<C, T>(this, ctx: C, body: (C) -> T) -> Result<JoinHandle<T>, SpawnError>
where C: Send, T: Send;
}
Configure the stack size and OS thread name before spawning:
mut b: Builder = Builder::new().stack_size(8 * 1024 * 1024).name(Str::new("worker"));
const h = b.spawn::<Job, Report>(job, run_job);
The name is capped at 15 bytes plus a terminator — the pthread_setname_np limit — and truncated past that. Each setter consumes and returns the builder, so the chain reads left to right; the terminal spawn consumes it too.
Scoped threads
type struct Scope {
tids: [u64];
stack_bytes: u64;
static new() -> Scope;
static new_with_stack(stack_bytes: u64) -> Scope;
spawn<C>(mut &this, ctx: C, body: (C) -> void) -> void
where C: Send;
join_all(mut &this) -> void;
}
A Scope guarantees every thread spawned through it is joined before the scope is torn down — explicitly with join_all(), or automatically when the scope drops. That is what makes it safe for scoped bodies to borrow data outliving the scope: pass a pointer to it through the body's context.
mut counter: i64 = 0i64;
mut s: thread::Scope = thread::Scope::new();
s.spawn::<i64*>(&counter, (p: i64*) -> void { *p = *p + 1i64; });
s.spawn::<i64*>(&counter, (p: i64*) -> void { *p = *p + 10i64; });
s.join_all(); // both finished; `counter` is safe to read
new_with_stack(stack_bytes) gives each thread a larger stack, for bodies that recurse deeply where the roughly 1 MiB default would overflow. Scoped bodies return void, so there is no result handshake.
Cryo has no borrow checker, so the join-before-teardown discipline — not the type system — is what keeps borrowed data alive. Do not let a scope outlive what its threads point at.
Trait implementations
implement trait Drop for struct Scope
Current thread
type struct Thread {
id_: ThreadId;
id(&this) -> ThreadId;
}
type struct ThreadId {
raw: u64;
equals(&this, other: &ThreadId) -> boolean;
}
current() returns a Thread with an id(). ThreadId compares through equals.
yield_now() gives up the rest of the timeslice. sleep(nanos) and sleep_ms(millis) park the calling thread; for a Duration-typed API see time::sleep. available_parallelism() is the number of hardware threads the process may use — sysconf(_SC_NPROCESSORS_ONLN) or the Win32 system info — and the natural size for a worker pool.
function current() -> Thread;
function current() -> Thread;The thread invoking this call.
function yield_now() -> void;
function yield_now() -> void;Yield the remainder of the current timeslice to the scheduler.
function sleep(nanos: u64) -> void;
function sleep(nanos: u64) -> void;Sleep the current thread for at least nanos nanoseconds. On POSIX this restarts across signal interruptions (EINTR) so the full duration elapses; on Windows it rounds up to whole milliseconds.
function sleep_ms(millis: u64) -> void;
function sleep_ms(millis: u64) -> void;Sleep for at least millis milliseconds.
function available_parallelism() -> u32;
function available_parallelism() -> u32;The number of hardware threads available to the process - the natural default size for a worker pool. Clamped to at least 1 so a pool always makes progress. Best-effort: returns 1 if the OS query fails.
How a spawn works
Each spawn heap-allocates a payload (the body function pointer plus the moved-in context) and a control block (an atomic state byte plus storage for the result). The OS runs a monomorphized trampoline that moves the context out, runs the body, stores the result, frees the payload, and hands the control block off to join or detach through a lock-free two-actor handshake on the state byte. The payload is freed on every path, spawn failure included, so the control block never leaks.
The sync primitives and Arc<T> back this surface.
public function name() { ... }
public:Visible to any module that imports this one - the default for top-level items and for struct fields, so it is mostly written to be explicit. public: opens a visibility block that groups the members after it.
public module collections::array;In a directory's _module.cryo aggregator, lists a submodule and whether it is public. Importing the directory's namespace sees only the modules declared public here.
import a::b;
import a::b::{ X, Y };
import a::b as c;
import a::b::*;Brings a module, or selected items from it, into scope. One path per declaration: use the brace form for several items, as to alias, and * for everything public (prefer the brace form; wildcards invite name collisions).
const NAME: Type = value;At module level, a true compile-time constant, conventionally SCREAMING_SNAKE_CASE. (mut at module level declares mutable global state instead.)
const h: JoinHandle<i32>function spawn<C, T>(ctx: C, body: (C) -> T) -> JoinHandle<T>
where C: Send, T: SendSpawn a thread that runs body(ctx) and returns a handle to its T result. ctx is moved into the new thread. body must be a function pointer or non-capturing lambda. Panics on allocation or pthread_create failure; use try_spawn for a fallible spawn.
(parameter) n: i32return value;Leaves the current function with the given value. A function whose return type is not void must return one; a bare return; is for void. In an async function it completes the future with the value.
(parameter) n: i32const r: i32const h: JoinHandle<i32>JoinHandle::join(mut this) -> TBlock until the thread finishes and return its result. Consumes the handle; the control block is freed here.
std::threadtype struct Name { ... }
type enum Name { ... }
type trait Name { ... }
type union Name { ... }Begins a type declaration; the keyword that follows says which kind. On its own, type Name = Existing; declares an alias.
type struct Name {
field: Type;
method(&this) -> Ret { ... }
}A value type with named fields and optional methods. Structs live on the stack and are passed by value. Fields are public by default and may carry = default values; methods take their receiver as &this, mut &this, or this.
type struct JoinHandle<T> {
tid: u64;
shared: Shared<T>*;
}Handle to a spawned thread. Carries the OS thread id and the shared control block. T is the body's return type; the context type C is erased (its storage was consumed on the thread).
type parameter TJoinHandle.tid: u64JoinHandle.shared: Shared<T>*type struct Shared<T> {
state: Atomic<u8>;
result: T;
}Heap control block shared between the spawned thread and the JoinHandle. Outlives the thread so join can read the result after the thread exits. result storage is uninitialized until the thread writes it; the state machine guarantees no actor reads or drops it before the write happens-before.
type parameter TJoinHandle::join(mut this) -> TBlock until the thread finishes and return its result. Consumes the handle; the control block is freed here.
mut thisA by-value receiver the body may also reassign. The receiver is moved into the method and consumed; the caller's value may not be used afterwards.
language reference&this
mut &this
thisThe receiver of a method. Its first parameter declares how the method takes it: &this borrows read-only, mut &this borrows exclusively, and this / mut this move the value in and consume it. In the body, this.field reaches the members.
JoinHandle::detach(mut this) -> voidDetach: give up the ability to join. The thread runs to completion independently; its result is dropped. Consumes the handle.
JoinHandle::is_finished(&this) -> booleanTrue once the thread has finished running its body. Does not consume the handle and never blocks.
JoinHandle::id(&this) -> ThreadIdThe spawned thread's id.
type struct ThreadId {
raw: u64;
}An opaque OS thread identity. Compare with == via equals.
type enum Name {
Unit,
Payload(T),
Named { x: i32 },
}An algebraic data type. Variants may be bare or carry a tuple or struct payload, and a match on an enum must cover every variant. Name::Variant names a variant; methods are added with implement enum Name { ... }.
type enum SpawnError {
OutOfMemory;
PermissionDenied;
ResourceLimit;
Other;
}Why a spawn failed.
SpawnError::OutOfMemorySpawnError::PermissionDeniedSpawnError::ResourceLimitSpawnError::Otherimplement struct Name { ... }
implement trait Trait for Type { ... }
implement u64 { ... }Adds methods to a type outside its declaration. implement struct T (or enum, or a bare primitive) opens an inherent block; implement trait Tr for T supplies a trait's methods for one type. Resolution is at compile time - no dynamic dispatch.
implement enum Name { ... }Names the enum an inherent block extends - the only way to give an enum methods.
language referencetype enum SpawnError {
OutOfMemory;
PermissionDenied;
ResourceLimit;
Other;
}Why a spawn failed.
std::threadstatic name(param: Type) -> Ret { ... }A method that belongs to the type rather than an instance, called as Type::name(..). For structs, static new(..) returning a struct literal is the idiomatic constructor.
static SpawnError::from_errno(rc: i32) -> SpawnErrorMap a pthread_create errno return to a SpawnError.
(parameter) rc: i32type struct Builder {
stack_bytes: u64;
name_buf: [u8;
16];
name_len: u64;
}A configurable thread spawn: set a stack size and/or an OS-visible thread name, then spawn (panics on failure) or try_spawn (returns a Result). Configuration is applied through a pthread_attr_t; the name is set with pthread_setname_np once the thread is running.
Builder.stack_bytes: u64Builder.name_buf: [u8Builder.name_len: u64static Builder::new() -> BuilderA builder with default attributes: OS-default stack, no name.
type struct Builder {
stack_bytes: u64;
name_buf: [u8;
16];
name_len: u64;
}A configurable thread spawn: set a stack size and/or an OS-visible thread name, then spawn (panics on failure) or try_spawn (returns a Result). Configuration is applied through a pthread_attr_t; the name is set with pthread_setname_np once the thread is running.
Builder::stack_size(mut this, bytes: u64) -> BuilderSet the new thread's stack size in bytes. The OS rounds up to a page and enforces a platform minimum (PTHREAD_STACK_MIN); a size below it surfaces as a spawn error rather than a silent clamp.
(parameter) bytes: u64Builder::name(mut this, n: Str) -> BuilderSet the OS-visible thread name, truncated to 15 bytes (the pthread_setname_np limit). Shows up in /proc/<pid>/task/*/comm, ps -T, top -H, and debuggers.
(parameter) n: Strtype struct Str {
bytes: u8*;
length: u64;
}Str: a borrowed UTF-8 string slice.
std::collections::strBuilder::spawn<C, T>(this, ctx: C, body: (C) -> T) -> JoinHandle<T>
where C: Send, T: SendSpawn with the configured attributes, panicking on failure. Use try_spawn for a fallible spawn.
type parameter Cwhere C: Send
type parameter Twhere T: Send
(parameter) ctx: C(parameter) body: (C) -> Ttype struct JoinHandle<T> {
tid: u64;
shared: Shared<T>*;
}Handle to a spawned thread. Carries the OS thread id and the shared control block. T is the body's return type; the context type C is erased (its storage was consumed on the thread).
where T: Ord + Clone, U: HashConstrains generic parameters to types that implement the listed traits, which is what makes those traits' methods callable on T. Bounds on one parameter join with +; parameters separate with ,. A where on a single method narrows the type's parameter for that method only.
type trait Send {}Types that are safe to transfer between threads. An owning value of a Send type may cross a thread boundary without violating memory safety.
Builder::try_spawn<C, T>(this, ctx: C, body: (C) -> T) -> Result<JoinHandle<T>, SpawnError>
where C: Send, T: SendSpawn with the configured attributes, returning Err(SpawnError) if the OS rejects the configuration (e.g. an out-of-range stack size) or cannot create the thread.
mut name: Type = value;A mutable binding, reassignable after declaration - mutability is opt-in, const is the default. At module level it declares mutable global state. Before & it marks an exclusive reference or receiver instead.
mut b: Builderstatic Builder::new() -> BuilderA builder with default attributes: OS-default stack, no name.
std::threadBuilder::stack_size(mut this, bytes: u64) -> BuilderSet the new thread's stack size in bytes. The OS rounds up to a page and enforces a platform minimum (PTHREAD_STACK_MIN); a size below it surfaces as a spawn error rather than a silent clamp.
Builder::name(mut this, n: Str) -> BuilderSet the OS-visible thread name, truncated to 15 bytes (the pthread_setname_np limit). Shows up in /proc/<pid>/task/*/comm, ps -T, top -H, and debuggers.
static Str::new(text: string) -> StrConstruct from a NUL-terminated C-style string literal. The caller certifies that text is valid UTF-8 and outlives this view. The NUL terminator is not included in the view.
const h: JoinHandle<Report>mut b: BuilderBuilder::spawn<C, T>(this, ctx: C, body: (C) -> T) -> JoinHandle<T>
where C: Send, T: SendSpawn with the configured attributes, panicking on failure. Use try_spawn for a fallible spawn.
type struct Scope {
tids: [u64];
stack_bytes: u64;
}A thread scope. Every thread spawned through it is guaranteed to be joined before the scope is torn down (explicitly via join_all() or automatically when the Scope drops), so scoped bodies may safely borrow data that outlives the scope - pass a pointer to it through the body's context:
Scope.tids: [u64]Scope.stack_bytes: u64static Scope::new() -> ScopeCreate an empty scope whose threads use the OS default stack size.
type struct Scope {
tids: [u64];
stack_bytes: u64;
}A thread scope. Every thread spawned through it is guaranteed to be joined before the scope is torn down (explicitly via join_all() or automatically when the Scope drops), so scoped bodies may safely borrow data that outlives the scope - pass a pointer to it through the body's context:
static Scope::new_with_stack(stack_bytes: u64) -> ScopeCreate a scope whose threads each get a stack_bytes-byte stack. Use this for bodies that recurse deeply (e.g. driving LLVM codegen), where the ~1 MiB default worker stack would overflow. 0 means OS default.
(parameter) stack_bytes: u64Scope::spawn<C>(mut &this, ctx: C, body: (C) -> void) -> void
where C: SendSpawn a scoped thread running body(ctx). ctx is moved in (often a pointer into scope-outliving state). The thread is recorded and joined by join_all() / the scope's drop. Panics on allocation or thread-creation failure.
mut &this
mut &TypeAn exclusive, mutating borrow. As a receiver, mut &this lets the method modify the fields - and callers can see that from the signature alone. As a parameter type, mut &T lets the callee mutate through the reference while the caller keeps ownership.
(parameter) body: (C) -> voidScope::join_all(mut &this) -> voidJoin every thread spawned so far, blocking until all have finished. Idempotent: clears the recorded set, so a later drop/join is a no-op.
mut counter: i64mut s: thread::Scopestatic Scope::new() -> ScopeCreate an empty scope whose threads use the OS default stack size.
std::threadmut s: thread::ScopeScope::spawn<C>(mut &this, ctx: C, body: (C) -> void) -> void
where C: SendSpawn a scoped thread running body(ctx). ctx is moved in (often a pointer into scope-outliving state). The thread is recorded and joined by join_all() / the scope's drop. Panics on allocation or thread-creation failure.
mut counter: i64(parameter) p: i64*(parameter) p: i64*(parameter) p: i64*Scope::join_all(mut &this) -> voidJoin every thread spawned so far, blocking until all have finished. Idempotent: clears the recorded set, so a later drop/join is a no-op.
std::threadtype struct Thread {
id_: ThreadId;
}A handle to a running thread (currently just its identity). Returned by thread::current().
Thread.id_: ThreadIdThread::id(&this) -> ThreadIdtype struct ThreadId {
raw: u64;
}An opaque OS thread identity. Compare with == via equals.
ThreadId.raw: u64ThreadId::equals(&this, other: &ThreadId) -> boolean(parameter) other: &ThreadIdtype struct ThreadLocal<T> {
key: u32;
}function name<T>(param: Type) -> Ret { ... }Declares a function. Parameters are written name: Type and are never inferred; the return type follows -> and defaults to void. Declaration order does not matter: every signature is collected before any body is checked, so functions may call each other freely and recurse.
function spawn<C, T>(ctx: C, body: (C) -> T) -> JoinHandle<T>
where C: Send, T: SendSpawn a thread that runs body(ctx) and returns a handle to its T result. ctx is moved into the new thread. body must be a function pointer or non-capturing lambda. Panics on allocation or pthread_create failure; use try_spawn for a fallible spawn.
Payload::body: (C) -> TPayload.ctx: CBuilder::try_spawn<C, T>(this, ctx: C, body: (C) -> T) -> Result<JoinHandle<T>, SpawnError>
where C: Send, T: SendSpawn with the configured attributes, returning Err(SpawnError) if the OS rejects the configuration (e.g. an out-of-range stack size) or cannot create the thread.
function try_spawn<C, T>(ctx: C, body: (C) -> T) -> Result<JoinHandle<T>, SpawnError>
where C: Send, T: SendFallible spawn: like spawn, but returns Err(SpawnError) instead of panicking when the OS cannot create the thread (e.g. EAGAIN under a thread-count limit). The body's T is still delivered through the returned handle's join().
Result::Err(E)function spawn_with_attr<C, T>(ctx: C, body: (C) -> T, stack_bytes: u64) -> Result<JoinHandle<T>, SpawnError>
where C: Send, T: SendFallible spawn with an optional stack size (0 == OS default). The per-OS thread creator (os_create_thread) applies it - via a pthread_attr_t on POSIX, or CreateThread's dwStackSize on Windows.
type struct String<A = GlobalAlloc> {
buffer: RawBuffer<u8, A>;
length: u64;
}String: owned, heap-allocated, length-typed UTF-8.
std::collections::stringtype struct Box<T, A = GlobalAlloc> where A: Allocator {
ptr: T*;
alloc: A;
}Box<T, A>: single-owner heap allocation.
std::alloc::boxtype struct Array<T, A = GlobalAlloc> where A: Allocator {
ptr: T*;
length: i64;
capacity: i64;
alloc: A;
}Array<T, A>: growable, heap-backed, contiguous sequence.
std::collections::arrayfunction spawn_with_attr<C, T>(ctx: C, body: (C) -> T, stack_bytes: u64) -> Result<JoinHandle<T>, SpawnError>
where C: Send, T: SendFallible spawn with an optional stack size (0 == OS default). The per-OS thread creator (os_create_thread) applies it - via a pthread_attr_t on POSIX, or CreateThread's dwStackSize on Windows.
JoinHandle::detach(mut this) -> voidDetach: give up the ability to join. The thread runs to completion independently; its result is dropped. Consumes the handle.
std::threadJoinHandle::is_finished(&this) -> booleanTrue once the thread has finished running its body. Does not consume the handle and never blocks.
std::threadstatic SpawnError::from_errno(rc: i32) -> SpawnErrorMap a pthread_create errno return to a SpawnError.
type trait Name : Base {
method(&this, other: &This) -> Ret;
}Names a set of methods a type may implement. Inside the body This is the implementing type; methods may carry default bodies, and a trait may require a base trait (type trait Ord : Eq). Generic code asks for a trait with where T: Name.
implement trait Trait for Type { ... }Names the type a trait implementation is for. The block that follows supplies the bodies the trait requires.
language referencestatic Scope::new_with_stack(stack_bytes: u64) -> ScopeCreate a scope whose threads each get a stack_bytes-byte stack. Use this for bodies that recurse deeply (e.g. driving LLVM codegen), where the ~1 MiB default worker stack would overflow. 0 means OS default.
Builder.stack_bytes: u64implement trait Trait for Type { ... }Introduces a trait implementation: the block supplies, for one concrete type, the method bodies the trait requires.
language referencetype struct Thread {
id_: ThreadId;
}A handle to a running thread (currently just its identity). Returned by thread::current().
ThreadId::equals(&this, other: &ThreadId) -> booleanfunction yield_now() -> voidYield the remainder of the current timeslice to the scheduler.
std::threadfunction sleep(nanos: u64) -> voidSleep the current thread for at least nanos nanoseconds. On POSIX this restarts across signal interruptions (EINTR) so the full duration elapses; on Windows it rounds up to whole milliseconds.
type struct Duration {
secs: u64;
nanos: u32;
}A length of time: whole secs plus a sub-second nanos remainder (0 <= nanos < 1_000_000_000). POD; copy-by-value, no drop.
function available_parallelism() -> u32The number of hardware threads available to the process - the natural default size for a worker pool. Clamped to at least 1 so a pool always makes progress. Best-effort: returns 1 if the OS query fails.
std::threadextern "C" function sysconf(name: i32) -> i64const _SC_NPROCESSORS_ONLN: i32 = 84;function current() -> ThreadThe thread invoking this call.
function yield_now() -> voidYield the remainder of the current timeslice to the scheduler.
function sleep(nanos: u64) -> voidSleep the current thread for at least nanos nanoseconds. On POSIX this restarts across signal interruptions (EINTR) so the full duration elapses; on Windows it rounds up to whole milliseconds.
(parameter) nanos: u64function sleep_ms(millis: u64) -> voidSleep for at least millis milliseconds.
(parameter) millis: u64function available_parallelism() -> u32The number of hardware threads available to the process - the natural default size for a worker pool. Clamped to at least 1 so a pool always makes progress. Best-effort: returns 1 if the OS query fails.