ksync is a Rust crate providing a token-based lock API, designed for low-level, no_std environments like the Zircon kernel.
ksync separates lock states from data. Instead of encapsulating protected data inside the lock struct itself (like std::sync::Mutex<T>), the data resides in separate fields of the same struct, wrapped in KCell. Access to these cells is granted only by presenting a LockToken (held by the guards returned when locking).
This design is particularly useful when:
Multiple fields need to be protected by the same lock but we want to keep them as distinct fields of the parent struct for clarity and layout control.
We need to support disjoint mutable borrows of different guarded fields simultaneously, which is normally difficult with a single standard lock guard.
#[guarded]The primary way to use this crate is via the #[guarded] attribute macro.
Apply #[guarded] to your struct and use the helper attributes:
#[mutex] to mark a KMutex field.#[brwlock] to mark a BrwLockPi (Reader-Writer) field.#[guarded_by(lock_field_name)] to mark fields protected by that lock.Under the hood, the macro:
KCell<T, LockClass>.MyStructMuGuard or MyStructLockReadGuard). These guards expose safe projection accessors directly.use ksync::{guarded, KMutex, lock}; #[derive(Default)] #[guarded] struct ImageCache { #[mutex] mu: KMutex, #[guarded_by(mu)] hits: u32, #[guarded_by(mu)] misses: u32, pub path: String, } fn update_cache(cache: &ImageCache, is_hit: bool) { // Acquire lock. lock!(let mut guard = cache.lock_mu()); // Access fields safely on the guard: if is_hit { *guard.as_mut().hits_mut() += 1; } else { *guard.as_mut().misses_mut() += 1; } // Or obtain disjoint mutable borrows of multiple fields simultaneously // using split accessors: let fields = guard.as_mut().fields_mut(); if is_hit { *fields.hits += 1; } else { *fields.misses += 1; } } fn main() { let cache = ImageCache { hits: 42.into(), // Under the hood, hits is a KCell, initialized via Into path: "/path/to/cache".to_string(), ..Default::default() }; update_cache(&cache, true); }
use ksync::{guarded, BrwLockPi, lock}; #[derive(Default)] #[guarded] struct Database { #[brwlock] lock: BrwLockPi, #[guarded_by(lock)] data: Vec<String>, #[guarded_by(lock)] query_count: u64, } fn read_data(db: &Database) -> usize { // Shared read access lock!(let guard = db.read_lock()); let len = guard.data().len(); // Access multiple fields via shared projection (disjoint read borrows): let fields = guard.fields(); let _ = fields.data.len(); let _ = *fields.query_count; len } fn append_data(db: &Database, value: String) { // Exclusive write access lock!(let mut guard = db.write_lock()); // Obtain disjoint mutable borrows of multiple fields simultaneously // using split/projection accessors: let fields = guard.as_mut().fields_mut(); fields.data.push(value); *fields.query_count += 1; }
If your struct has multiple locks, the macro will automatically generate a distinct, unique lock class for each lock field. You can lock them independently and access their respective fields:
use ksync::{guarded, KMutex, lock}; #[derive(Default)] #[guarded] struct DualCache { #[mutex] mu1: KMutex, #[mutex] mu2: KMutex, #[guarded_by(mu1)] data1: u32, #[guarded_by(mu2)] data2: i32, } fn process_dual_cache(cache: &DualCache) { // Lock both mutexes independently. lock!(let mut guard1 = cache.lock_mu1()); lock!(let mut guard2 = cache.lock_mu2()); // Access individual fields safely via guards: *guard1.as_mut().data1_mut() = 100; *guard2.as_mut().data2_mut() = -50; // Split accessors work for each guard independently as well: let fields1 = guard1.as_mut().fields_mut(); *fields1.data1 += 10; } fn main() { let cache = DualCache::default(); process_dual_cache(&cache); }
When porting C++ code that utilizes Clang's Thread Safety Analysis (e.g., methods annotated with TA_REQ(mu)) to Rust, this crate provides a safe analog by declaring these lock-required methods directly on the generated Guard structure instead of the parent struct.
This design pattern:
The generated Guard structure holds a private reference parent pointing to the physical parent structure instance. Since the Guard is generated inside the same module, you can explicitly access all un-guarded fields and call lock-free parent methods through self.parent:
self.parent.remote_addressself.parent.is_local()For guarded fields, the Guard's inherent target accessors (like self.bytes_sent()) or split accessors (self.fields_mut()) provide safe access.
#[derive(Default)] #[guarded] struct Connection { #[mutex] mu: KMutex, #[guarded_by(mu)] bytes_sent: u64, // Un-guarded state pub remote_address: String, } impl Connection { // Lock-free method on the parent struct pub fn is_local(&self) -> bool { self.remote_address.starts_with("127.0.0.1") } } // Locked methods declared on the generated Guard! impl<'a> ConnectionMuGuard<'a> { pub fn send_packet(mut self: core::pin::Pin<&mut Self>, size: u64) { // 1. Access and mutate guarded fields: let fields = self.as_mut().fields_mut(); *fields.bytes_sent += size; // 2. Explicitly read un-guarded fields via self.parent: if self.parent.remote_address.is_empty() { return; } // 3. Explicitly call lock-free methods via self.parent: if self.parent.is_local() { // ... } } } fn main() { use ksync::lock; let mut conn = Connection { remote_address: "127.0.0.1:8080".to_string(), ..Default::default() }; // Scoped lock and execution: lock!(let mut guard = conn.lock_mu()); guard.as_mut().send_packet(1024); }