├── .gitignore ├── Cargo.toml ├── README.md ├── src └── lib.rs └── tests ├── cfg.rs ├── nostd.rs └── with_display.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "atomic_enum" 3 | version = "0.3.0" 4 | authors = ["Thomas Bächler "] 5 | edition = "2021" 6 | 7 | description = "An attribute to create an atomic wrapper around a C-style enum" 8 | repository = "https://github.com/brain0/atomic_enum" 9 | keywords = ["atomic", "enum"] 10 | categories = ["concurrency"] 11 | license = "MIT" 12 | 13 | [badges] 14 | maintenance = { status = "passively-maintained" } 15 | 16 | [lib] 17 | proc-macro = true 18 | 19 | [features] 20 | default = ["cas"] 21 | cas = [] # enables compare-and-swap operations on atomics 22 | 23 | [dependencies] 24 | syn = { version = "2.0.50", features = ["full"] } 25 | quote = "1.0.35" 26 | proc-macro2 = "1.0.78" 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![cargo version](https://img.shields.io/crates/v/atomic_enum.svg)](https://crates.io/crates/atomic_enum) 2 | [![docs.rs version](https://img.shields.io/docsrs/atomic_enum)](https://docs.rs/atomic_enum/latest/atomic_enum/) 3 | # atomic_enum 4 | 5 | An attribute to create an atomic wrapper around a C-style enum. 6 | 7 | Internally, the generated wrapper uses an `AtomicUsize` to store the value. 8 | The atomic operations have the same semantics as the equivalent operations 9 | of `AtomicUsize`. 10 | 11 | # Example 12 | ```rust 13 | # use atomic_enum::atomic_enum; 14 | # use std::sync::atomic::Ordering; 15 | #[atomic_enum] 16 | #[derive(PartialEq)] 17 | enum CatState { 18 | Dead = 0, 19 | BothDeadAndAlive, 20 | Alive, 21 | } 22 | 23 | let state = AtomicCatState::new(CatState::Dead); 24 | state.store(CatState::Alive, Ordering::Relaxed); 25 | assert_eq!(state.load(Ordering::Relaxed), CatState::Alive); 26 | ``` 27 | 28 | This attribute does not use or generate any unsafe code. 29 | 30 | The crate can be used in a `#[no_std]` environment. 31 | 32 | # Maintenance Note 33 | This crate is passively maintained. -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![forbid( 2 | rust_2018_idioms, 3 | future_incompatible, 4 | elided_lifetimes_in_paths, 5 | unsafe_code 6 | )] 7 | #![warn( 8 | missing_debug_implementations, 9 | missing_docs, 10 | trivial_casts, 11 | trivial_numeric_casts, 12 | unreachable_pub, 13 | unused_import_braces, 14 | unused_qualifications 15 | )] 16 | 17 | //! An attribute to create an atomic wrapper around a C-style enum. 18 | //! 19 | //! Internally, the generated wrapper uses an `AtomicUsize` to store the value. 20 | //! The atomic operations have the same semantics as the equivalent operations 21 | //! of `AtomicUsize`. 22 | //! 23 | //! # Example 24 | //! 25 | //! ``` 26 | //! # use atomic_enum::atomic_enum; 27 | //! # use std::sync::atomic::Ordering; 28 | //! #[atomic_enum] 29 | //! #[derive(PartialEq)] 30 | //! enum CatState { 31 | //! Dead = 0, 32 | //! BothDeadAndAlive, 33 | //! Alive, 34 | //! } 35 | //! 36 | //! let state = AtomicCatState::new(CatState::Dead); 37 | //! state.store(CatState::Alive, Ordering::Relaxed); 38 | //! 39 | //! assert_eq!(state.load(Ordering::Relaxed), CatState::Alive); 40 | //! ``` 41 | //! 42 | //! This attribute does not use or generate any unsafe code. 43 | //! 44 | //! The crate can be used in a `#[no_std]` environment. 45 | 46 | use proc_macro::TokenStream; 47 | use proc_macro2::TokenStream as TokenStream2; 48 | use quote::{quote, quote_spanned}; 49 | use syn::{parse_macro_input, spanned::Spanned, Attribute, Ident, ItemEnum, Variant, Visibility}; 50 | 51 | fn enum_definition<'a>( 52 | attrs: impl IntoIterator, 53 | vis: &Visibility, 54 | ident: &Ident, 55 | variants: impl IntoIterator, 56 | ) -> TokenStream2 { 57 | let attrs = attrs.into_iter(); 58 | let variants = variants.into_iter(); 59 | 60 | quote! { 61 | #(#attrs)* 62 | #[derive(Debug, Clone, Copy)] 63 | #vis enum #ident { 64 | #( #variants ),* 65 | } 66 | } 67 | } 68 | 69 | fn atomic_enum_definition(vis: &Visibility, ident: &Ident, atomic_ident: &Ident) -> TokenStream2 { 70 | let atomic_ident_docs = format!( 71 | "A wrapper around [`{0}`] which can be safely shared between threads. 72 | 73 | This type uses an `AtomicUsize` to store the enum value. 74 | 75 | [`{0}`]: enum.{0}.html", 76 | ident 77 | ); 78 | 79 | quote! { 80 | #[doc = #atomic_ident_docs] 81 | #vis struct #atomic_ident(core::sync::atomic::AtomicUsize); 82 | } 83 | } 84 | 85 | fn enum_to_usize(ident: &Ident) -> TokenStream2 { 86 | quote! { 87 | const fn to_usize(val: #ident) -> usize { 88 | val as usize 89 | } 90 | } 91 | } 92 | 93 | fn enum_from_usize(ident: &Ident, variants: impl IntoIterator) -> TokenStream2 { 94 | let variants_with_const_names: Vec<_> = variants 95 | .into_iter() 96 | .map(|v| { 97 | let c_id = Ident::new(&format!("USIZE_{}", &v.ident), v.ident.span()); 98 | (v.attrs, v.ident, c_id) 99 | }) 100 | .collect(); 101 | 102 | let variant_consts = variants_with_const_names 103 | .iter() 104 | .map(|(attrs, id, c_id)| quote! { #(#attrs)* const #c_id: usize = #ident::#id as usize; }); 105 | 106 | let variants_back = variants_with_const_names 107 | .iter() 108 | .map(|(attrs, id, c_id)| quote! { #(#attrs)* #c_id => #ident::#id, }); 109 | 110 | quote! { 111 | fn from_usize(val: usize) -> #ident { 112 | #![allow(non_upper_case_globals)] 113 | #(#variant_consts)* 114 | 115 | match val { 116 | #(#variants_back)* 117 | _ => panic!("Invalid enum discriminant"), 118 | } 119 | } 120 | } 121 | } 122 | 123 | fn atomic_enum_new(ident: &Ident, atomic_ident: &Ident) -> TokenStream2 { 124 | let atomic_ident_docs = format!( 125 | "Creates a new atomic [`{0}`]. 126 | 127 | [`{0}`]: enum.{0}.html", 128 | ident 129 | ); 130 | 131 | quote! { 132 | #[doc = #atomic_ident_docs] 133 | pub const fn new(v: #ident) -> #atomic_ident { 134 | #atomic_ident(core::sync::atomic::AtomicUsize::new(Self::to_usize(v))) 135 | } 136 | } 137 | } 138 | 139 | fn atomic_enum_into_inner(ident: &Ident) -> TokenStream2 { 140 | quote! { 141 | /// Consumes the atomic and returns the contained value. 142 | /// 143 | /// This is safe because passing self by value guarantees that no other threads are concurrently accessing the atomic data. 144 | pub fn into_inner(self) -> #ident { 145 | Self::from_usize(self.0.into_inner()) 146 | } 147 | } 148 | } 149 | 150 | fn atomic_enum_set(ident: &Ident) -> TokenStream2 { 151 | quote! { 152 | /// Sets the value of the atomic without performing an atomic operation. 153 | /// 154 | /// This is safe because the mutable reference guarantees that no other threads are concurrently accessing the atomic data. 155 | pub fn set(&mut self, v: #ident) { 156 | *self.0.get_mut() = Self::to_usize(v); 157 | } 158 | } 159 | } 160 | 161 | fn atomic_enum_get(ident: &Ident) -> TokenStream2 { 162 | quote! { 163 | /// Gets the value of the atomic without performing an atomic operation. 164 | /// 165 | /// This is safe because the mutable reference guarantees that no other threads are concurrently accessing the atomic data. 166 | pub fn get(&mut self) -> #ident { 167 | Self::from_usize(*self.0.get_mut()) 168 | } 169 | } 170 | } 171 | 172 | fn atomic_enum_swap_mut(ident: &Ident) -> TokenStream2 { 173 | quote! { 174 | /// Stores a value into the atomic, returning the previous value, without performing an atomic operation. 175 | /// 176 | /// This is safe because the mutable reference guarantees that no other threads are concurrently accessing the atomic data. 177 | pub fn swap_mut(&mut self, v: #ident) -> #ident { 178 | let r = self.get(); 179 | self.set(v); 180 | r 181 | } 182 | } 183 | } 184 | 185 | fn atomic_enum_load(ident: &Ident) -> TokenStream2 { 186 | quote! { 187 | /// Loads a value from the atomic. 188 | /// 189 | /// `load` takes an `Ordering` argument which describes the memory ordering of this operation. Possible values are `SeqCst`, `Acquire` and `Relaxed`. 190 | /// 191 | /// # Panics 192 | /// 193 | /// Panics if order is `Release` or `AcqRel`. 194 | pub fn load(&self, order: core::sync::atomic::Ordering) -> #ident { 195 | Self::from_usize(self.0.load(order)) 196 | } 197 | } 198 | } 199 | 200 | fn atomic_enum_store(ident: &Ident) -> TokenStream2 { 201 | quote! { 202 | /// Stores a value into the atomic. 203 | /// 204 | /// `store` takes an `Ordering` argument which describes the memory ordering of this operation. Possible values are `SeqCst`, `Release` and `Relaxed`. 205 | /// 206 | /// # Panics 207 | /// 208 | /// Panics if order is `Acquire` or `AcqRel`. 209 | pub fn store(&self, val: #ident, order: core::sync::atomic::Ordering) { 210 | self.0.store(Self::to_usize(val), order) 211 | } 212 | } 213 | } 214 | 215 | #[cfg(feature = "cas")] 216 | fn atomic_enum_swap(ident: &Ident) -> TokenStream2 { 217 | quote! { 218 | /// Stores a value into the atomic, returning the previous value. 219 | /// 220 | /// `swap` takes an `Ordering` argument which describes the memory ordering of this operation. 221 | /// All ordering modes are possible. Note that using `Acquire` makes the store part of this operation `Relaxed`, 222 | /// and using `Release` makes the load part `Relaxed`. 223 | pub fn swap(&self, val: #ident, order: core::sync::atomic::Ordering) -> #ident { 224 | Self::from_usize(self.0.swap(Self::to_usize(val), order)) 225 | } 226 | } 227 | } 228 | 229 | #[cfg(feature = "cas")] 230 | fn atomic_enum_compare_and_swap(ident: &Ident) -> TokenStream2 { 231 | quote! { 232 | /// Stores a value into the atomic if the current value is the same as the `current` value. 233 | /// 234 | /// The return value is always the previous value. If it is equal to `current`, then the value was updated. 235 | /// 236 | /// `compare_and_swap` also takes an `Ordering` argument which describes the memory ordering of this operation. 237 | /// Notice that even when using `AcqRel`, the operation might fail and hence just perform an `Acquire` load, but 238 | /// not have `Release` semantics. Using `Acquire` makes the store part of this operation `Relaxed` if it happens, 239 | /// and using `Release` makes the load part `Relaxed`. 240 | #[allow(deprecated)] 241 | #[deprecated(note = "Use `compare_exchange` or `compare_exchange_weak` instead")] 242 | pub fn compare_and_swap( 243 | &self, 244 | current: #ident, 245 | new: #ident, 246 | order: core::sync::atomic::Ordering 247 | ) -> #ident { 248 | Self::from_usize(self.0.compare_and_swap( 249 | Self::to_usize(current), 250 | Self::to_usize(new), 251 | order 252 | )) 253 | } 254 | } 255 | } 256 | 257 | #[cfg(feature = "cas")] 258 | fn atomic_enum_compare_exchange(ident: &Ident) -> TokenStream2 { 259 | quote! { 260 | /// Stores a value into the atomic if the current value is the same as the `current` value. 261 | /// 262 | /// The return value is a result indicating whether the new value was written and containing the previous value. 263 | /// On success this value is guaranteed to be equal to `current`. 264 | /// 265 | /// `compare_exchange` takes two `Ordering` arguments to describe the memory ordering of this operation. The first 266 | /// describes the required ordering if the operation succeeds while the second describes the required ordering when 267 | /// the operation fails. Using `Acquire` as success ordering makes the store part of this operation `Relaxed`, and 268 | /// using `Release` makes the successful load `Relaxed`. The failure ordering can only be `SeqCst`, `Acquire` or 269 | /// `Relaxed` and must be equivalent to or weaker than the success ordering. 270 | pub fn compare_exchange( 271 | &self, 272 | current: #ident, 273 | new: #ident, 274 | success: core::sync::atomic::Ordering, 275 | failure: core::sync::atomic::Ordering 276 | ) -> core::result::Result<#ident, #ident> { 277 | self.0 278 | .compare_exchange( 279 | Self::to_usize(current), 280 | Self::to_usize(new), 281 | success, 282 | failure 283 | ) 284 | .map(Self::from_usize) 285 | .map_err(Self::from_usize) 286 | } 287 | } 288 | } 289 | 290 | #[cfg(feature = "cas")] 291 | fn atomic_enum_compare_exchange_weak(ident: &Ident) -> TokenStream2 { 292 | quote! { 293 | /// Stores a value into the atomic if the current value is the same as the `current` value. 294 | /// 295 | /// Unlike `compare_exchange`, this function is allowed to spuriously fail even when the comparison succeeds, 296 | /// which can result in more efficient code on some platforms. The return value is a result indicating whether 297 | /// the new value was written and containing the previous value. 298 | /// 299 | /// `compare_exchange_weak` takes two `Ordering` arguments to describe the memory ordering of this operation. 300 | /// The first describes the required ordering if the operation succeeds while the second describes the required 301 | /// ordering when the operation fails. Using `Acquire` as success ordering makes the store part of this operation 302 | /// `Relaxed`, and using `Release` makes the successful load `Relaxed`. The failure ordering can only be `SeqCst`, 303 | /// `Acquire` or `Relaxed` and must be equivalent to or weaker than the success ordering. 304 | pub fn compare_exchange_weak( 305 | &self, 306 | current: #ident, 307 | new: #ident, 308 | success: core::sync::atomic::Ordering, 309 | failure: core::sync::atomic::Ordering 310 | ) -> core::result::Result<#ident, #ident> { 311 | self.0 312 | .compare_exchange_weak( 313 | Self::to_usize(current), 314 | Self::to_usize(new), 315 | success, 316 | failure 317 | ) 318 | .map(Self::from_usize) 319 | .map_err(Self::from_usize) 320 | } 321 | } 322 | } 323 | 324 | fn from_impl(ident: &Ident, atomic_ident: &Ident) -> TokenStream2 { 325 | quote! { 326 | impl From<#ident> for #atomic_ident { 327 | fn from(val: #ident) -> #atomic_ident { 328 | #atomic_ident::new(val) 329 | } 330 | } 331 | } 332 | } 333 | 334 | fn debug_impl(atomic_ident: &Ident) -> TokenStream2 { 335 | quote! { 336 | impl core::fmt::Debug for #atomic_ident { 337 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { 338 | core::fmt::Debug::fmt(&self.load(core::sync::atomic::Ordering::SeqCst), f) 339 | } 340 | } 341 | } 342 | } 343 | 344 | #[proc_macro_attribute] 345 | /// Creates an atomic wrapper around a C-style enum. 346 | /// 347 | /// The generated type is a wrapper around `AtomicUsize` that transparently 348 | /// converts between the stored integer and the enum type. This attribute 349 | /// also automatically derives the `Debug`, `Copy` and `Clone` traits on 350 | /// the enum type. 351 | /// 352 | /// The name of the atomic type is the name of the enum type, prefixed with 353 | /// `Atomic`. 354 | /// 355 | /// ``` 356 | /// # use atomic_enum::atomic_enum; 357 | /// #[atomic_enum] 358 | /// enum State { 359 | /// On, 360 | /// Off, 361 | /// } 362 | /// 363 | /// let state = AtomicState::new(State::Off); 364 | /// ``` 365 | /// 366 | /// The name can be overridden by passing an identifier 367 | /// as an argument to the attribute. 368 | /// 369 | /// ``` 370 | /// # use atomic_enum::atomic_enum; 371 | /// #[atomic_enum(StateAtomic)] 372 | /// enum State { 373 | /// On, 374 | /// Off, 375 | /// } 376 | /// 377 | /// let state = StateAtomic::new(State::Off); 378 | /// ``` 379 | pub fn atomic_enum(args: TokenStream, input: TokenStream) -> TokenStream { 380 | // Parse the input 381 | let ItemEnum { 382 | attrs, 383 | vis, 384 | ident, 385 | generics, 386 | variants, 387 | .. 388 | } = parse_macro_input!(input as ItemEnum); 389 | 390 | // We only support C-style enums: No generics, no fields 391 | if !generics.params.is_empty() { 392 | let span = generics.span(); 393 | let err = quote_spanned! {span=> compile_error!("Expected an enum without generics."); }; 394 | return err.into(); 395 | } 396 | 397 | for variant in variants.iter() { 398 | if !matches!(variant.fields, syn::Fields::Unit) { 399 | let span = variant.fields.span(); 400 | let err = 401 | quote_spanned! {span=> compile_error!("Expected a variant without fields."); }; 402 | return err.into(); 403 | } 404 | } 405 | 406 | // Define the enum 407 | let mut output = enum_definition(attrs, &vis, &ident, &variants); 408 | 409 | // Define the atomic wrapper 410 | let atomic_ident = parse_macro_input!(args as Option) 411 | .unwrap_or_else(|| Ident::new(&format!("Atomic{}", ident), ident.span())); 412 | output.extend(atomic_enum_definition(&vis, &ident, &atomic_ident)); 413 | 414 | // Write the impl block for the atomic wrapper 415 | let enum_to_usize = enum_to_usize(&ident); 416 | let enum_from_usize = enum_from_usize(&ident, variants); 417 | let atomic_enum_new = atomic_enum_new(&ident, &atomic_ident); 418 | let atomic_enum_into_inner = atomic_enum_into_inner(&ident); 419 | let atomic_enum_set = atomic_enum_set(&ident); 420 | let atomic_enum_get = atomic_enum_get(&ident); 421 | let atomic_enum_swap_mut = atomic_enum_swap_mut(&ident); 422 | let atomic_enum_load = atomic_enum_load(&ident); 423 | let atomic_enum_store = atomic_enum_store(&ident); 424 | 425 | output.extend(quote! { 426 | impl #atomic_ident { 427 | #enum_to_usize 428 | #enum_from_usize 429 | #atomic_enum_new 430 | #atomic_enum_into_inner 431 | #atomic_enum_set 432 | #atomic_enum_get 433 | #atomic_enum_swap_mut 434 | #atomic_enum_load 435 | #atomic_enum_store 436 | } 437 | }); 438 | 439 | #[cfg(feature = "cas")] 440 | { 441 | let atomic_enum_swap = atomic_enum_swap(&ident); 442 | let atomic_enum_compare_and_swap = atomic_enum_compare_and_swap(&ident); 443 | let atomic_enum_compare_exchange = atomic_enum_compare_exchange(&ident); 444 | let atomic_enum_compare_exchange_weak = atomic_enum_compare_exchange_weak(&ident); 445 | 446 | output.extend(quote! { 447 | impl #atomic_ident { 448 | #atomic_enum_swap 449 | #atomic_enum_compare_and_swap 450 | #atomic_enum_compare_exchange 451 | #atomic_enum_compare_exchange_weak 452 | } 453 | }); 454 | } 455 | 456 | // Implement the from and debug traits 457 | output.extend(from_impl(&ident, &atomic_ident)); 458 | output.extend(debug_impl(&atomic_ident)); 459 | 460 | output.into() 461 | } 462 | -------------------------------------------------------------------------------- /tests/cfg.rs: -------------------------------------------------------------------------------- 1 | #![allow(unexpected_cfgs)] // multics is deliberately always false 2 | 3 | use atomic_enum::atomic_enum; 4 | 5 | #[atomic_enum] 6 | enum MyEnum { 7 | Foo, 8 | #[cfg(target_os = "multics")] 9 | Bar, 10 | #[cfg(not(target_os = "multics"))] 11 | Baz 12 | } 13 | 14 | // Foo and Baz should both be constructible. Bar should not be, but that can only be verified from 15 | // a doc test. 16 | #[test] 17 | fn construction() { 18 | let _ = AtomicMyEnum::new(MyEnum::Foo); 19 | let _ = AtomicMyEnum::new(MyEnum::Baz); 20 | } 21 | -------------------------------------------------------------------------------- /tests/nostd.rs: -------------------------------------------------------------------------------- 1 | // Purpose: This integration test checks that atomic_enum can be used in 2 | // a no_std environment. 3 | 4 | #![no_std] 5 | 6 | use core::sync::atomic::Ordering; 7 | 8 | use atomic_enum::atomic_enum; 9 | 10 | #[atomic_enum] 11 | #[derive(PartialEq, Eq)] 12 | enum FooBar { 13 | Foo, 14 | Bar, 15 | } 16 | 17 | #[cfg(feature = "cas")] 18 | #[test] 19 | fn test_no_std_use() { 20 | let fb = AtomicFooBar::new(FooBar::Foo); 21 | let prev = fb 22 | .compare_exchange( 23 | FooBar::Foo, 24 | FooBar::Bar, 25 | Ordering::SeqCst, 26 | Ordering::Relaxed, 27 | ) 28 | .unwrap(); 29 | assert_eq!(prev, FooBar::Foo); 30 | 31 | let prev_fail = fb.compare_exchange( 32 | FooBar::Foo, 33 | FooBar::Bar, 34 | Ordering::SeqCst, 35 | Ordering::Relaxed, 36 | ); 37 | assert!(prev_fail.is_err()); 38 | } 39 | 40 | mod result_conflict { 41 | use atomic_enum::atomic_enum; 42 | use core::sync::atomic::Ordering; 43 | 44 | #[allow(dead_code)] 45 | pub type Result = core::result::Result; 46 | 47 | #[atomic_enum] 48 | #[derive(PartialEq, Eq)] 49 | enum FooBar { 50 | Foo, 51 | Bar, 52 | } 53 | 54 | #[test] 55 | fn test_result_conflict() { 56 | let fb = AtomicFooBar::new(FooBar::Foo); 57 | assert_eq!(fb.load(Ordering::SeqCst), FooBar::Foo); 58 | } 59 | } 60 | 61 | #[test] 62 | fn test_load_store() { 63 | let original = FooBar::Foo; 64 | let fb = AtomicFooBar::new(original); 65 | assert_eq!(fb.load(Ordering::SeqCst), original); 66 | 67 | let new = FooBar::Bar; 68 | fb.store(new, Ordering::SeqCst); 69 | assert_eq!(fb.load(Ordering::SeqCst), new); 70 | } 71 | -------------------------------------------------------------------------------- /tests/with_display.rs: -------------------------------------------------------------------------------- 1 | use core::sync::atomic::Ordering; 2 | use std::fmt; 3 | use std::fmt::{Display, Formatter}; 4 | 5 | use atomic_enum::atomic_enum; 6 | 7 | #[atomic_enum] 8 | #[derive(PartialEq, Eq)] 9 | enum DisplayableEnum { 10 | Foo, 11 | } 12 | 13 | impl Display for DisplayableEnum { 14 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 15 | match self { 16 | DisplayableEnum::Foo => write!(f, "Foo"), 17 | } 18 | } 19 | } 20 | 21 | #[test] 22 | fn test_displayable_enum() { 23 | let e = AtomicDisplayableEnum::new(DisplayableEnum::Foo); 24 | assert_eq!(format!("{}", e.load(Ordering::SeqCst)), "Foo"); 25 | } 26 | --------------------------------------------------------------------------------