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}