Crate tokenlock[−][src]
This crate provides a cell type, TokenLock, whose contents can only be
accessed by an unforgeable token.
Examples
Basics
let mut token = ArcToken::new(); let lock = TokenLock::new(token.id(), 1); assert_eq!(*lock.read(&token), 1); let mut guard = lock.write(&mut token); assert_eq!(*guard, 1); *guard = 2;
Only the original Token's owner can access its contents. Token
cannot be cloned:
let lock = Arc::new(TokenLock::new(token.id(), 1)); let lock_1 = Arc::clone(&lock); thread::Builder::new().spawn(move || { let lock_1 = lock_1; let mut token_1 = token; // I have `Token` so I can get a mutable reference to the contents lock_1.write(&mut token_1); }).unwrap(); // can't access the contents; I no longer have `Token` // lock.write(&mut token);
Lifetimes
The lifetime of the returned reference is limited by both of the TokenLock
and Token.
let mut token = ArcToken::new(); let lock = TokenLock::new(token.id(), 1); let guard = lock.write(&mut token); drop(lock); // compile error: `guard` cannot outlive `TokenLock` drop(guard);
drop(token); // compile error: `guard` cannot outlive `Token` drop(guard);
It also prevents from forming a reference to the contained value when there already is a mutable reference to it:
let write_guard = lock.write(&mut token); let read_guard = lock.read(&token); // compile error drop(write_guard);
While allowing multiple immutable references:
let read_guard1 = lock.read(&token); let read_guard2 = lock.read(&token);
Use case: Linked lists
An operating system kernel often needs to store the global state in a global
variable. Linked lists are a common data structure used in a kernel, but
Rust's ownership does not allow forming 'static references into values
protected by a mutex. Common work-arounds, such as smart pointers and index
references, take a heavy toll on a small microcontroller with a single-issue
in-order pipeline and no hardware multiplier.
struct Process { prev: Option<& /* what lifetime? */ Process>, next: Option<& /* what lifetime? */ Process>, state: u8, /* ... */ } struct SystemState { first_process: Option<& /* what lifetime? */ Process>, process_pool: [Process; 64], } static STATE: Mutex<SystemState> = todo!();
tokenlock makes the 'static reference approach possible by detaching the
lock granularity from the protected data's granularity.
use tokenlock::*; use std::cell::Cell; struct Tag; impl_singleton_token_factory!(Tag); type KLock<T> = UnsyncTokenLock<T, SingletonTokenId<Tag>>; type KLockToken = UnsyncSingletonToken<Tag>; type KLockTokenId = SingletonTokenId<Tag>; struct Process { prev: KLock<Option<&'static Process>>, next: KLock<Option<&'static Process>>, state: KLock<u8>, /* ... */ } struct SystemState { first_process: KLock<Option<&'static Process>>, process_pool: [Process; 1], } static STATE: SystemState = SystemState { first_process: KLock::new(KLockTokenId::new(), None), process_pool: [ Process { prev: KLock::new(KLockTokenId::new(), None), next: KLock::new(KLockTokenId::new(), None), state: KLock::new(KLockTokenId::new(), 0), } ], };
Token types
This crate provides the following types implementing Token.
(std only) RcToken and ArcToken ensure their uniqueness by
reference-counted memory allocations.
SingletonToken<Tag> is a singleton token, meaning only one of such
instance can exist at any point of time during the program's execution.
impl_singleton_token_factory! instantiates a static flag to indicate
SingletonToken's liveness and allows you to construct it safely by
SingletonToken::new. Alternatively, you can use
SingletonToken::new_unchecked, but this is unsafe if misused.
!Sync tokens
UnsyncTokenLock is similar to TokenLock but designed for non-Sync
tokens and has relaxed requirements on the inner type for thread safety.
Specifically, it can be Sync even if the inner type is not Sync. This
allows for storing non-Sync cells such as Cell and reading and
writing them using shared references (all of which must be on the same
thread because the token is !Sync) to the token.
use std::cell::Cell; let mut token = ArcToken::new(); let lock = Arc::new(UnsyncTokenLock::new(token.id(), Cell::new(1))); let lock_1 = Arc::clone(&lock); thread::Builder::new().spawn(move || { // "Lock" the token to the current thread using // `ArcToken::borrow_as_unsync` let token = token.borrow_as_unsync(); // Shared references can alias let (token_1, token_2) = (&token, &token); lock_1.read(token_1).set(2); lock_1.read(token_2).set(4); }).unwrap();
!Sync tokens, of course, cannot be shared between threads:
let mut token = ArcToken::new(); let token = token.borrow_as_unsync(); let (token_1, token_2) = (&token, &token); // compile error: `&ArcTokenUnsyncRef` is not `Send` because // `ArcTokenUnsyncRef` is not `Sync` thread::Builder::new().spawn(move || { let _ = token_2; }); let _ = token_1;
Macros
| impl_singleton_token_factory | Implement |
Structs
| ArcToken | An |
| ArcTokenId | Token that cannot be used to access the contents of a |
| ArcTokenUnsyncRef | Represents a borrow of |
| BadTokenError | Error type returned when a key ( |
| RcToken | An |
| RcTokenId | Token that cannot be used to access the contents of a |
| SingletonToken | A singleton unforgeable token used to access the contents of a
|
| SingletonTokenExhaustedError | Error type returned when |
| SingletonTokenGuard | The RAII guard for a |
| SingletonTokenId | Token that cannot be used to access the contents of a |
| SingletonTokenRef | Zero-sized logical equivalent of |
| SingletonTokenRefMut | Zero-sized logical equivalent of |
| TokenLock | A mutual exclusive primitive that can be accessed using a |
| UnsyncTokenLock | Like |
Traits
| SingletonTokenFactory | Associates a type with a flag indicating whether an instance of
|
| Token | Trait for an unforgeable token used to access the contents of a
|
| Unsync | Asserts the types implementing this trait are |
Type Definitions
| UnsyncSingletonToken | The |
| UnsyncSingletonTokenGuard | The |
| UnsyncSingletonTokenRef | Zero-sized logical equivalent of |
| UnsyncSingletonTokenRefMut | Zero-sized logical equivalent of |