Real-time
Audio callbacks, control loops, and frame hot paths cannot afford a hidden allocation or a lock. C+ lets you mark such a function and have the compiler prove the property holds across the entire transitive call graph. A violation is a compile error, not a lint you can ignore.
The near-term target is soft real-time: audio callbacks, game frame loops, robotics control loops, market-data hot paths, and embedded-style firmware on a normal OS. Hard real-time needs scheduler, interrupt, and hardware guarantees that live below the language.
The #[realtime] bundle
#[realtime] is sugar for a bundle of smaller contracts:
#[no_alloc]— no heap allocation#[no_block]— no blocking call or lock wait#[bounded_recursion]— no unbounded recursion
Bounded stack usage is checked alongside it. Each smaller attribute is available on its own, because many code paths need only one part of the contract.
#[realtime]
fn process_frame(input: f32x4[], output: f32x4[]) {
let n: usize = output.len();
let mut i: usize = 0 as usize;
while i < n {
output[i] = input[i] * GAIN; // pure SIMD math
i = i +% (1 as usize);
}
// a Vec::new, lock, channel recv, sleep, or unknown extern here
// would not compile (E0901 / E0907)
return;
}
#[no_alloc] — proven allocation-free (E0901)
A #[no_alloc] function rejects any path that reaches the heap. Direct malloc, calloc, realloc, aligned_alloc, and free reject, as do calls through #[link_name = "malloc"]. The check is resolved from sema information, not text, so it also catches allocation through a method call, through string interpolation ("...${x}..."), and through the Vec / HashMap / Box / Arc / string constructors. The blessed to_string() is rejected at its call site because it allocates.
The contract is transitive: a function called from a #[no_alloc] body must itself be #[no_alloc], and an unknown extern is treated as "not proven" and rejected. The escape hatch is to vouch for a known-safe extern explicitly:
#[no_alloc]
extern fn sinf(x: f32) -> f32;
One hole is documented and deferred: a
Dropdestructor that itself allocates and is run implicitly at scope exit is not yet caught. In practice destructors free rather than allocate, so this is narrow, but it is not yet enforced.
#[no_block] — proven non-blocking (E0907)
A #[no_block] function rejects mutex locks, condition-variable waits, thread joins, sleep and timer waits, blocking file and socket I/O, unbounded channel recv, and unknown externs. It allows plain arithmetic, stack memory, atomics, atomic_thread_fence, #cpu_relax, and nonblocking try-style APIs. Like #[no_alloc], it composes transitively.
#[max_stack(N)] — bounded frame size (E0908)
#[max_stack(4096)]
fn callback(...) { ... }
The checker estimates the frame from the parameters and typed locals across all nested blocks, using ABI-accurate sizes for primitives, pointers, arrays, structs, enums, and SIMD types, and reports E0908 when the estimate exceeds N. Large local arrays and by-value temporaries become visible in the diagnostic.
Project-wide: [profile.realtime]
Rather than annotate every function, a project can opt in globally from its manifest:
[profile.realtime]
deny_alloc = true
deny_block = true
deny_unknown_extern = true
stack_limit = 4096
When present, the driver synthesizes the matching contracts onto every function defined in this package (dependencies are exempt), and the same E0901 / E0907 / E0908 diagnostics do the enforcement. cpc check runs the whole-project front-end gate without codegen, the fast CI check, and cpc check --diagnostics=json emits one machine-readable diagnostic per line for editor and CI tooling.
Threads: Send and Sync (E0502)
The marker types are enforced structurally. Rc[T] is !Send and !Sync, and MutexGuard[T] is !Send, so handing one to a Send-bounded site such as thread::spawn is E0502. Arc[T] stays Send + Sync as the thread-safe sibling.
Two tightenings are deferred: raw-pointer-containing structs defaulting to
!Send(needs anunsafe impl Sendopt-in that does not exist yet, so enabling it would reject most FFI code with no recourse), and structural propagation of!Sendthrough a struct that holds anRc.
Building blocks
The contracts say what a hot path may not do; these packages give it allocation-free, lock-free tools to work with:
- rt — a lock-free SPSC ring (
SpscRingU64) and a fixed-capacity object pool (FixedPoolU64). Every method is#[no_alloc]and#[no_block], so they are callable from inside a#[realtime]body. - static-arena — a fixed-size, stack-resident arena with zero
mallocand zerofree. rt_darwin(see the rt page) — the macOS platform controls: a monotonic clock, audio-QoS thread priority, and page locking, each returningResult.rt_linuxandrt_posixwill mirror it.