# 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
```toml
[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:
```rust
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:
```rust
use surelock::key_handle::KeyHandle;
let mut handle = KeyHandle::claim();
});
```
`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 `KeyHandle`s 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:
```rust
#[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:
```rust
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:
```toml
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`](https://crates.io/crates/cortex-m) crate provides one:
```toml
[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:
```toml
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`:
```toml
surelock = { version = "0.1", default-features = false, features = ["portable-atomic"] }
```
## Summary
| 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 |