atomic_enums/lib.rs
1#![cfg_attr(not(test), no_std)]
2//! This crate provides an `AtomicEnum`.
3//! This can only be used with C like enumerations.
4//!
5//! The `gen_atomic_enum!` macro is provided which can be used to create a valid enumeration.
6use core::fmt::Debug;
7use core::marker::PhantomData;
8use core::sync::atomic::{self, Ordering};
9
10/// The trait must be implemented for enumerations which shall be used with an `AtomicEnum`.
11/// Additionally the traits `Into<u16>` and `TryFrom<u16>` have to be implemented.
12pub trait Atomize<T>: TryFrom<T> + Into<T> {}
13
14/// This trait must be implemented for the underlying atomic type.
15///
16/// The trait is already implemented for:
17/// - `AtomicU8`
18/// - `AtomicU16`
19/// - `AtomicU32`
20/// - `AtomicU64` with the `u64` feature.
21/// - `AtomicUsize` with the `usize` feature.
22pub trait AtomicOps<T> {
23 fn atomic_new(v: T) -> Self;
24
25 fn atomic_load(&self, order: Ordering) -> T;
26
27 fn atomic_store(&self, v: T, order: Ordering);
28
29 fn atomic_swap(&self, v: T, order: Ordering) -> T;
30
31 fn atomic_compare_exchange(
32 &self,
33 curr: T,
34 new: T,
35 success: Ordering,
36 failure: Ordering,
37 ) -> Result<T, T>;
38}
39
40/// The `AtomicEnum` is used to store values of an C like enumeration in
41/// an atomic type.
42pub struct AtomicEnum<E, A, U>(A, PhantomData<E>, PhantomData<U>);
43
44impl<E, A, U> AtomicEnum<E, A, U>
45where
46 E: TryFrom<U> + Into<U>,
47 A: AtomicOps<U>,
48 U: Copy,
49{
50 /// Create a new atomic enumeration.
51 ///
52 /// ## Params
53 /// - v: The value with which the enumeration is to be initialized
54 ///
55 /// ## Returns
56 /// A new `AtomicEnum`
57 ///
58 /// ## Example
59 /// ```
60 /// use atomic_enums::{gen_atomic_enum, AtomicEnumU32};
61 ///
62 /// gen_atomic_enum!(State, u32:
63 /// Running: 2
64 /// Paused: 3
65 /// );
66 ///
67 /// impl TryFrom<u32> for State {
68 /// type Error = ();
69 ///
70 /// fn try_from(v: u32) -> Result<Self, Self::Error> {
71 /// match v {
72 /// 2 => Ok(Self::Running),
73 /// 3 => Ok(Self::Paused),
74 /// _ => Err(()),
75 /// }
76 /// }
77 /// }
78 ///
79 /// let state = AtomicEnumU32::new(State::Running);
80 /// /* Do whatever you want to do... */
81 /// ```
82 pub fn new(v: E) -> Self {
83 Self(A::atomic_new(v.into()), PhantomData, PhantomData)
84 }
85
86 /// Load the currently stored value of the atomic enum
87 ///
88 /// The following is copyed from the offical documentation of [`atomic::AtomicU32`].
89 ///
90 /// *`load` takes an [`Ordering`] argument which describes the memory ordering of this operation.
91 /// Possible values are [`Ordering::SeqCst`], [`Ordering::Acquire`] and [`Ordering::Relaxed`].*
92 ///
93 /// ## Panics
94 ///
95 /// *Panics if `order` is [`Ordering::Release`] or [`Ordering::AcqRel`].*
96 ///
97 /// ## Example
98 /// ```
99 /// use atomic_enums::{gen_atomic_enum, AtomicEnumU32};
100 ///
101 /// use core::sync::atomic::Ordering::Relaxed;
102 ///
103 /// gen_atomic_enum!(State, u32:
104 /// Running: 2
105 /// Paused: 3
106 /// );
107 ///
108 /// impl TryFrom<u32> for State {
109 /// type Error = ();
110 ///
111 /// fn try_from(v: u32) -> Result<Self, Self::Error> {
112 /// match v {
113 /// 2 => Ok(Self::Running),
114 /// 3 => Ok(Self::Paused),
115 /// _ => Err(()),
116 /// }
117 /// }
118 /// }
119 ///
120 /// let state = AtomicEnumU32::new(State::Paused);
121 ///
122 /// assert_eq!(state.load(Relaxed).unwrap(), State::Paused);
123 /// ```
124 pub fn load(&self, order: Ordering) -> Option<E> {
125 match self.0.atomic_load(order).try_into() {
126 Ok(e) => Some(e),
127 Err(_) => None,
128 }
129 }
130
131 /// Store the passed value in the atomic enumeration
132 ///
133 /// The following is copyed from the offical documentation of [`atomic::AtomicU32::store`].
134 ///
135 /// `store` takes an [`Ordering`] argument which describes the memory ordering of this operation.
136 ///
137 /// *Possible values are [`Ordering::SeqCst`], [`Ordering::Release`] and [`Ordering::Relaxed`].*
138 ///
139 /// ## Panics
140 ///
141 /// *Panics if `order` is [`Ordering::Acquire`] or [`Ordering::AcqRel`].*
142 ///
143 /// ## Example
144 /// ```
145 /// use atomic_enums::{gen_atomic_enum, AtomicEnumU32};
146 ///
147 /// use core::sync::atomic::Ordering::Relaxed;
148 ///
149 /// gen_atomic_enum!(State, u32:
150 /// Running: 2
151 /// Paused: 3
152 /// );
153 ///
154 /// impl TryFrom<u32> for State {
155 /// type Error = ();
156 ///
157 /// fn try_from(v: u32) -> Result<Self, Self::Error> {
158 /// match v {
159 /// 2 => Ok(Self::Running),
160 /// 3 => Ok(Self::Paused),
161 /// _ => Err(()),
162 /// }
163 /// }
164 /// }
165 ///
166 /// let state = AtomicEnumU32::new(State::Paused);
167 ///
168 /// state.store(State::Running, Relaxed);
169 ///
170 /// assert_eq!(state.load(Relaxed).unwrap(), State::Running);
171 /// ```
172 pub fn store(&self, val: E, order: Ordering) {
173 self.0.atomic_store(val.into(), order)
174 }
175
176 /// Stores the passed enum value and returns the previous value.
177 ///
178 /// The following is copyed from the offical documentation of [`atomic::AtomicU32::swap`].
179 ///
180 /// *`swap` takes an [`Ordering`] argument which describes the memory ordering
181 /// of this operation. All ordering modes are possible. Note that using
182 /// [`Ordering::Acquire`] makes the store part of this operation [`Ordering::Relaxed`], and
183 /// using [`Ordering::Release`] makes the load part [`Ordering::Relaxed`].*
184 ///
185 /// ## Example
186 /// ```
187 /// use atomic_enums::{gen_atomic_enum, AtomicEnumU32};
188 ///
189 /// use core::sync::atomic::Ordering::Relaxed;
190 ///
191 /// gen_atomic_enum!(State, u32:
192 /// Running: 2
193 /// Paused: 3
194 /// );
195 ///
196 /// impl TryFrom<u32> for State {
197 /// type Error = ();
198 ///
199 /// fn try_from(v: u32) -> Result<Self, Self::Error> {
200 /// match v {
201 /// 2 => Ok(Self::Running),
202 /// 3 => Ok(Self::Paused),
203 /// _ => Err(()),
204 /// }
205 /// }
206 /// }
207 ///
208 /// let state = AtomicEnumU32::new(State::Paused);
209 ///
210 /// assert_eq!(state.swap(State::Running, Relaxed).unwrap(), State::Paused);
211 /// assert_eq!(state.load(Relaxed).unwrap(), State::Running);
212 /// ```
213 pub fn swap(&self, val: E, order: Ordering) -> Option<E> {
214 match self.0.atomic_swap(val.into(), order).try_into() {
215 Ok(en) => Some(en),
216 Err(_) => None,
217 }
218 }
219
220 /// Stores the `new` value, if the `current` value ist equal to the currently stored value.
221 ///
222 /// The following is copyed from the offical documentation of [`atomic::AtomicU32::compare_exchange`].
223 ///
224 /// *The return value is a result indicating whether the new value was written and
225 /// containing the previous value. On success this value is guaranteed to be equal to
226 /// `current`.*
227 ///
228 /// *`compare_exchange` takes two [`Ordering`] arguments to describe the memory
229 /// ordering of this operation. `success` describes the required ordering for the
230 /// read-modify-write operation that takes place if the comparison with `current` succeeds.
231 /// `failure` describes the required ordering for the load operation that takes place when
232 /// the comparison fails. Using [`Ordering::Acquire`] as success ordering makes the store part
233 /// of this operation [`Ordering::Relaxed`], and using [`Ordering::Release`] makes the successful load
234 /// [`Ordering::Relaxed`].
235 /// The failure ordering can only be [`Ordering::SeqCst`], [`Ordering::Acquire`] or [`Ordering::Relaxed`].*
236 ///
237 /// ## Example
238 /// ```
239 /// use atomic_enums::{gen_atomic_enum, AtomicEnumU32};
240 ///
241 /// use core::sync::atomic::Ordering::Relaxed;
242 ///
243 /// gen_atomic_enum!(State, u32:
244 /// Running: 2
245 /// Paused: 3
246 /// );
247 ///
248 /// impl TryFrom<u32> for State {
249 /// type Error = ();
250 ///
251 /// fn try_from(v: u32) -> Result<Self, Self::Error> {
252 /// match v {
253 /// 2 => Ok(Self::Running),
254 /// 3 => Ok(Self::Paused),
255 /// _ => Err(()),
256 /// }
257 /// }
258 /// }
259 ///
260 /// let state = AtomicEnumU32::new(State::Paused);
261 ///
262 /// let mut result = state.compare_exchange(
263 /// State::Paused,
264 /// State::Running,
265 /// Relaxed,
266 /// Relaxed,
267 /// );
268 /// assert_eq!(result.unwrap().unwrap(), State::Paused);
269 ///
270 /// result = state.compare_exchange(
271 /// State::Paused,
272 /// State::Running,
273 /// Relaxed,
274 /// Relaxed,
275 /// );
276 ///
277 /// assert_eq!(result.unwrap_err().unwrap(), State::Running);
278 /// ```
279 pub fn compare_exchange(
280 &self,
281 current: E,
282 new: E,
283 success: Ordering,
284 failure: Ordering,
285 ) -> Result<Option<E>, Option<E>> {
286 match self
287 .0
288 .atomic_compare_exchange(current.into(), new.into(), success, failure)
289 {
290 Ok(v) => match v.try_into() {
291 Ok(e) => Ok(Some(e)),
292 Err(_) => Ok(None),
293 },
294 Err(v) => match v.try_into() {
295 Ok(e) => Err(Some(e)),
296 Err(_) => Err(None),
297 },
298 }
299 }
300}
301
302impl<E, A, U> Debug for AtomicEnum<E, A, U>
303where
304 E: TryFrom<U> + Into<U> + Debug,
305 A: AtomicOps<U>,
306 U: Copy,
307{
308 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
309 let mut dbg_info = f.debug_tuple("AtomicEnum");
310 let tmp = self.load(Ordering::Relaxed);
311
312 match tmp {
313 Some(v) => dbg_info.field(&v),
314 None => dbg_info.field(&"Invalid value!"),
315 };
316
317 dbg_info.finish()
318 }
319}
320
321/// This macro can be used to generate a C like enumeration,
322/// which automaticly implements `impl From<YourStruct> for <youre base type> { ... }` and `Atomize`.
323/// You must implement the trait `impl TryFrom<youre base type> for YourStruct` to use the enumeration.
324///
325/// ## Params
326/// 1. The name, which the enumeration
327/// 2. The underlying type ([`u8`], [`u16`], [`u32`], [`u64`], [`usize`])
328/// 3. List of, `EnumerationField`: `number`
329///
330/// ## Example
331/// ```
332/// use atomic_enums::gen_atomic_enum;
333///
334/// gen_atomic_enum!(State, u32:
335/// Running: 2
336/// Paused: 3
337/// );
338///
339/// impl TryFrom<u32> for State {
340/// type Error = ();
341///
342/// fn try_from(v: u32) -> Result<Self, Self::Error> {
343/// match v {
344/// 2 => Ok(Self::Running),
345/// 3 => Ok(Self::Paused),
346/// _ => Err(()),
347/// }
348/// }
349/// }
350///
351/// assert_eq!(State::Running as u32, 2);
352/// assert_eq!(State::Paused as u32, 3);
353/// ```
354#[macro_export]
355macro_rules! gen_atomic_enum {
356 ($name:ident, $b_ty:ty: $($val:ident: $num:expr)*) => {
357 #[repr($b_ty)]
358 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
359 enum $name {
360 $(
361 $val = $num,
362 )*
363 }
364
365 impl From<$name> for $b_ty {
366 fn from(value: $name) -> Self {
367 value as $b_ty
368 }
369 }
370
371 impl atomic_enums::Atomize<$b_ty> for $name {}
372 };
373}
374
375macro_rules! gen_atomic_ops_impls {
376 ($($at:ty, $typ:ty, $name:ident)*) => {
377 $(
378 pub type $name<E> = AtomicEnum<E, $at, $typ>;
379
380 impl AtomicOps<$typ> for $at {
381 fn atomic_new(v: $typ) -> Self {
382 Self::new(v)
383 }
384
385 fn atomic_compare_exchange(&self, curr: $typ, new: $typ, success: Ordering, failure: Ordering) -> Result<$typ, $typ> {
386 self.compare_exchange(curr, new, success, failure)
387 }
388
389 fn atomic_load(&self, order: Ordering) -> $typ {
390 self.load(order)
391 }
392
393 fn atomic_store(&self, v: $typ, order: Ordering) {
394 self.store(v, order)
395 }
396
397 fn atomic_swap(&self, v: $typ, order: Ordering) -> $typ {
398 self.swap(v, order)
399 }
400 }
401 )*
402 };
403}
404
405gen_atomic_ops_impls!(
406 atomic::AtomicU8, u8, AtomicEnumU8
407 atomic::AtomicU16, u16, AtomicEnumU16
408 atomic::AtomicU32, u32, AtomicEnumU32
409 atomic::AtomicUsize, usize, AtomicEnumUsize
410);
411
412#[cfg(feature = "u64")]
413gen_atomic_ops_impls!(atomic::AtomicU64, u64, AtomicEnumU64);
414
415#[cfg(test)]
416mod tests {
417 use core::{
418 marker::PhantomData,
419 sync::atomic::Ordering::Relaxed,
420 };
421
422 use paste::item;
423
424 use super::*;
425
426 macro_rules! gen_tests {
427 ($($bty:ty, $aty:ty, $abasety:ty)*) => {
428 $(
429 impl TryFrom<$bty> for TestEnum {
430 type Error = ();
431
432 fn try_from(value: $bty) -> Result<Self, Self::Error> {
433 match value {
434 1 => Ok(Self::Init),
435 2 => Ok(Self::Idle),
436 3 => Ok(Self::Running),
437 4 => Ok(Self::Stopped),
438 _ => Err(())
439 }
440 }
441 }
442
443 impl From<TestEnum> for $bty {
444 fn from(value: TestEnum) -> Self {
445 value as Self
446 }
447 }
448
449 item!{
450 #[test]
451 fn [<new_$bty>]() {
452 let new_enum = $aty::new(TestEnum::Init);
453
454 assert_eq!(new_enum.0.load(Relaxed), TestEnum::Init.into());
455 }
456
457 #[test]
458 fn [<load_$bty>]() {
459 let test_enum: $aty<TestEnum> = init_enum(TestEnum::Idle.into());
460
461 let result = test_enum.load(Relaxed);
462 assert!(result.is_some(), "Must return Some(TestEnum::Idle)");
463
464 let result = result.unwrap();
465 assert_eq!(result, TestEnum::Idle);
466 }
467
468 #[test]
469 fn [<store_$bty>]() {
470 let test_enum: $aty<TestEnum> = init_enum(TestEnum::Stopped.into());
471
472 test_enum.store(TestEnum::Idle, Relaxed);
473
474 assert_eq!(test_enum.0.load(Relaxed), TestEnum::Idle.into());
475 }
476
477 #[test]
478 fn [<cmp_exc_false_$bty>]() {
479 let test_enum: $aty<TestEnum> = init_enum(TestEnum::Running.into());
480
481 let result =
482 test_enum.compare_exchange(TestEnum::Idle, TestEnum::Running, Relaxed, Relaxed);
483 assert!(result.is_err());
484
485 let result = result.unwrap_err();
486 assert!(result.is_some());
487
488 let result = result.unwrap();
489 assert_eq!(result, TestEnum::Running);
490
491 assert_eq!(test_enum.0.load(Relaxed), TestEnum::Running.into())
492 }
493
494 #[test]
495 fn [<cmp_exc_true_$bty>]() {
496 let test_enum: $aty<TestEnum> = init_enum(TestEnum::Running.into());
497
498 let result =
499 test_enum.compare_exchange(TestEnum::Running, TestEnum::Idle, Relaxed, Relaxed);
500 assert!(result.is_ok());
501
502 let result = result.unwrap();
503 assert!(result.is_some());
504
505 let result = result.unwrap();
506 assert_eq!(result, TestEnum::Running);
507
508 assert_eq!(test_enum.0.load(Relaxed), TestEnum::Idle.into());
509 }
510
511 #[test]
512 fn [<swap_$bty>]() {
513 let test_enum: $aty<TestEnum> = init_enum(TestEnum::Init.into());
514
515 let result = test_enum.swap(TestEnum::Stopped, Relaxed);
516
517 assert!(result.is_some());
518
519 let result = result.unwrap();
520 assert_eq!(result, TestEnum::Init);
521
522 assert_eq!(test_enum.0.load(Relaxed), TestEnum::Stopped.into());
523 }
524
525 #[test]
526 fn [<load_invalid_$bty>]() {
527 let test_enum: $aty<TestEnum> = init_enum(32);
528
529 let result = test_enum.load(Relaxed);
530
531 assert!(result.is_none());
532 }
533
534 #[test]
535 fn [<swap_comp_invalid_$bty>]() {
536 let test_enum: $aty<TestEnum> = init_enum(255);
537
538 let result = test_enum.swap(TestEnum::Running, Relaxed);
539
540 assert!(result.is_none());
541 assert_eq!(test_enum.0.load(Relaxed), TestEnum::Running.into());
542 }
543
544 #[test]
545 fn [<compare_exchange_false_invalid_$bty>]() {
546 let test_enum: $aty<TestEnum> = init_enum(64);
547
548 let result = test_enum.compare_exchange(TestEnum::Running, TestEnum::Idle, Relaxed, Relaxed);
549
550 assert!(result.is_err());
551 assert!(result.unwrap_err().is_none());
552 }
553 }
554 )*
555 };
556 }
557
558 fn init_enum<A: AtomicOps<U>, U>(val: U) -> AtomicEnum<TestEnum, A, U> {
559 AtomicEnum {
560 0: A::atomic_new(val),
561 1: PhantomData,
562 2: PhantomData,
563 }
564 }
565
566 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
567 enum TestEnum {
568 Init = 1,
569 Idle = 2,
570 Running = 3,
571 Stopped = 4,
572 }
573
574 gen_tests!(
575 u8, AtomicEnumU8, atomic::AtomicU8
576 u16, AtomicEnumU16, atomic::AtomicU16
577 u32, AtomicEnumU32, atomic::AtomicU32
578 usize, AtomicEnumUsize, atomic::AtomicUsize
579 );
580
581 #[cfg(feature = "u64")]
582 gen_tests!(u64, AtomicEnumU64, atomic::AtomicU64);
583}