Surelock no_std Guide#
Surelock supports no_std environments when default features are disabled. The core library requires only alloc. This guide covers what changes on no_std and what patterns to follow.
Feature Configuration#
[dependencies]
# Minimal no_std (AtomicU32 for LockId, works on Cortex-M4+, RISC-V with CAS, etc.)
surelock = { version = "0.1", default-features = false }
# no_std with u64 LockId (requires native AtomicU64)
surelock = { version = "0.1", default-features = false, features = ["atomic-u64"] }
# no_std for Cortex-M0 / thumbv6m (no native CAS)
surelock = { version = "0.1", default-features = false, features = ["cortex-m"] }
# no_std for other targets without CAS
surelock = { version = "0.1", default-features = false, features = ["critical-section"] }
# no_std with lock_api backend (parking_lot, spin, etc.)
surelock = { version = "0.1", default-features = false, features = ["lock-api"] }
What's Different on no_std#
No Default Mutex Backend#
On std, Mutex<u32> defaults to StdMutex (wrapping std::sync::Mutex). On no_std, there is no default backend -- you must specify one:
use surelock::mutex::Mutex;
// With lock-api feature + spin backend
type SpinMutex<T> = Mutex<T, surelock::level::Base, spin::mutex::SpinMutex<()>>;
let counter: SpinMutex<u32> = Mutex::new(0);
No lock_scope / try_lock_scope#
These ambient entry points are #[cfg(feature = "std")] only. They use thread_local! for nesting prevention, which isn't available on no_std.
On no_std, use KeyHandle directly:
use surelock::key_handle::KeyHandle;
let mut handle = KeyHandle::claim();
handle.scope(|key| {
// lock things here
});
scope(&mut self) prevents nesting at compile time via the borrow checker -- no thread_local! needed.
No thread_local! Scope Uniqueness#
On std, KeyHandle::try_claim() checks a thread_local! flag and returns None if a handle already exists on the current thread. On no_std, try_claim() always returns Some -- there is no per-context uniqueness check.
This means multiple KeyHandles can coexist on the same core on no_std. Each handle's scope(&mut self) prevents nesting within that handle, but two handles on the same core could produce independent keys that defeat the ordering guarantee.
Recommended Patterns#
Single-Core (Cortex-M0, Cortex-M4, etc.)#
Create one KeyHandle in main() and thread &mut handle to wherever you need scopes:
#[entry]
fn main() -> ! {
let mut handle = KeyHandle::claim();
loop {
do_work(&mut handle);
}
}
fn do_work(handle: &mut KeyHandle) {
handle.scope(|key| {
// lock things
});
}
One core, one handle, one scope at a time. The &mut borrow ensures this at compile time.
Multi-Core (Cortex-A, RISC-V SMP, etc.)#
Use Locksmith to distribute one KeyHandle per core during init:
use surelock::locksmith::Locksmith;
static SMITH: Locksmith = Locksmith::create(NUM_CORES);
fn core0_init() {
// Issue a voucher for each other core
let voucher_1 = SMITH.issue().unwrap();
send_to_core(1, voucher_1);
// Claim a handle for this core
let mut handle = SMITH.issue().unwrap().redeem().unwrap();
handle.scope(|key| { /* ... */ });
}
fn core1_main(voucher: KeyVoucher) {
let mut handle = voucher.redeem().unwrap();
handle.scope(|key| { /* ... */ });
}
The Locksmith limits total issuance to NUM_CORES. Each core redeems its voucher for a KeyHandle. The discipline is: send exactly one voucher per core during init.
What To Avoid#
Do not call KeyHandle::claim() more than once per core. On no_std, claim() always succeeds -- there is no check for an existing handle. Two handles on the same core can produce independent keys that bypass ordering enforcement.
If you need nested locking contexts, use key.subscope() within an existing scope -- not a second KeyHandle.
Targets Without CAS (thumbv6m)#
Cortex-M0 and similar targets lack hardware compare-and-swap (CAS) operations. Surelock uses fetch_add, compare_exchange, and fetch_update on atomics, which require CAS.
For Cortex-M targets, use the cortex-m convenience feature:
surelock = { version = "0.1", default-features = false, features = ["cortex-m"] }
This enables portable-atomic with critical-section support. You will also need a critical-section implementation for your target. For Cortex-M, the cortex-m crate provides one:
[dependencies]
surelock = { version = "0.1", default-features = false, features = ["cortex-m"] }
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
For other targets without CAS, use the critical-section feature directly and provide your own critical-section implementation:
surelock = { version = "0.1", default-features = false, features = ["critical-section"] }
For targets that have native CAS but lack AtomicU64 (e.g., some RISC-V), use portable-atomic without critical-section:
surelock = { version = "0.1", default-features = false, features = ["portable-atomic"] }
Summary#
| Concern | std |
no_std |
|---|---|---|
| Default backend | StdMutex |
Must specify (e.g., spin) |
| Scope entry | lock_scope / KeyHandle |
KeyHandle only |
| Nesting prevention | thread_local! + &mut |
&mut only (compile-time) |
| Per-context uniqueness | thread_local! flag |
Setup discipline |
| Multi-core distribution | One KeyHandle per thread (auto) |
Locksmith + KeyVoucher (explicit) |
| Targets without CAS | N/A | Enable portable-atomic feature |