atomic_int/
lib.rs

1/*
2 * Copyright 2023, 2025 taylor.fish <contact@taylor.fish>
3 *
4 * This file is part of atomic-int.
5 *
6 * atomic-int is licensed under the Apache License, Version 2.0
7 * (the "License"); you may not use atomic-int except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 *     http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19#![cfg_attr(not(feature = "libc"), no_std)]
20#![cfg_attr(feature = "doc_cfg", feature(doc_cfg))]
21#![deny(unsafe_op_in_unsafe_fn)]
22
23//! atomic-int provides atomics for additional integers, such as C/FFI types
24//! like [`c_int`].
25//!
26//! For integer types that are aliases of primitive integers that have built-in
27//! Rust atomics, this crate simply re-exports those atomics. Otherwise, this
28//! crate provides a spinlock-based fallback implementation with a compatible
29//! API.
30//!
31//! This crate also provides types that directly correspond with Rust’s
32//! standard atomics, like [`AtomicU64`], with the difference that the fallback
33//! implementation will similarly be used for any such atomics that are not
34//! supported on a given platform. Thus, all atomics provided by this crate are
35//! available on all platforms[^1] in some form—either the built-in or fallback
36//! implementation.
37//!
38//! [^1]: As long as the platform supports [`AtomicBool`], and compare-and-swap
39//!       operations on [`AtomicBool`], which are required for the fallback
40//!       implementation.
41//!
42//! Crate features
43//! --------------
44//!
45//! Types that directly correspond with Rust’s standard atomics like
46//! [`AtomicU64`] are available with the feature `primitives` (enabled by
47//! default). This includes [`AtomicPtr`], even though it isn’t exactly an
48//! integer.
49//!
50//! Atomic C integer types like [`AtomicCInt`] and [`AtomicCUlong`] are
51//! available with the feature `c` (enabled by default). For more granularity,
52//! a separate feature exists for each C integer (e.g., `c_int` and `c_ulong`).
53//!
54//! The spinlock-based fallback implementation can cause deadlocks with signal
55//! handlers. To avoid this, enable the feature `signal`, which blocks incoming
56//! signals while the lock is held. This feature is Unix-specific; on
57//! non-Unix-like operating systems it is a no-op.
58//!
59//! atomic-int can optionally depend on [`libc`]. If this dependency is
60//! enabled, atomic-int will use the C integer types from [`libc`] instead of
61//! [`core::ffi`]. This should not make a noticeable difference, but it can
62//! decrease the minimum required Rust version, as C integer types were added
63//! to [`core::ffi`] only in version 1.64. The feature `signal` always enables
64//! `libc`.
65//!
66//! For development purposes, the feature `force-fallback` is provided. This
67//! forces the fallback implementation to be used for all atomics, which can
68//! help you ensure your program doesn’t rely on functionality only provided by
69//! the native atomic types. It should not normally be enabled outside of
70//! testing.
71//!
72//! Use without `std`
73//! -----------------
74//!
75//! This crate is `no_std` when `libc` is not enabled.
76//!
77//! [`libc`]: https://docs.rs/libc/0.2
78//! [`c_int`]: ffi::c_int
79//! [`AtomicBool`]: atomic::AtomicBool
80
81#[allow(unused_imports)]
82use core::sync::atomic;
83
84#[cfg(not(feature = "libc"))]
85use core::ffi;
86
87#[cfg(feature = "libc")]
88use libc as ffi;
89
90#[allow(unused_imports)]
91use ffi as _;
92
93mod detail {
94    pub trait GetAtomicOrFallback {
95        type Type;
96    }
97
98    pub struct AtomicOrFallback<T, Fallback, const HAS_ATOMIC: bool>(
99        core::marker::PhantomData<fn() -> (T, Fallback)>,
100    );
101
102    impl<T, Fallback> GetAtomicOrFallback
103        for AtomicOrFallback<T, Fallback, false>
104    {
105        type Type = Fallback;
106    }
107}
108
109#[allow(dead_code)]
110use detail::{AtomicOrFallback, GetAtomicOrFallback};
111
112#[allow(dead_code)]
113struct AtomicMeta<T>(core::marker::PhantomData<fn() -> T>);
114
115#[allow(dead_code)]
116trait DefaultAtomicMeta {
117    const HAS_ATOMIC: bool = false;
118}
119
120impl<T> DefaultAtomicMeta for AtomicMeta<T> {}
121
122macro_rules! with_primitive_atomics {
123    ($macro:path) => {
124        $macro!(AtomicI8, i8, [target_has_atomic = "8"], "");
125        $macro!(AtomicU8, u8, [target_has_atomic = "8"], "");
126        $macro!(AtomicI16, i16, [target_has_atomic = "16"], "");
127        $macro!(AtomicU16, u16, [target_has_atomic = "16"], "");
128        $macro!(AtomicI32, i32, [target_has_atomic = "32"], "");
129        $macro!(AtomicU32, u32, [target_has_atomic = "32"], "");
130        $macro!(AtomicI64, i64, [target_has_atomic = "64"], "");
131        $macro!(AtomicU64, u64, [target_has_atomic = "64"], "");
132        $macro!(
133            AtomicI128,
134            i128,
135            [any()],
136            "**Note:** Because 128-bit atomics are unstable, this type is \
137            always a spinlock-based fallback. This may change in a future \
138            version of this crate."
139        );
140        $macro!(
141            AtomicU128,
142            u128,
143            [any()],
144            "**Note:** Because 128-bit atomics are unstable, this type is \
145            always a spinlock-based fallback. This may change in a future \
146            version of this crate."
147        );
148        $macro!(AtomicIsize, isize, [target_has_atomic = "ptr"], "");
149        $macro!(AtomicUsize, usize, [target_has_atomic = "ptr"], "");
150    };
151}
152
153macro_rules! impl_atomic_meta {
154    ($atomic:ident, $int:ident, [$($cfg:tt)*], $($x:tt)*) => {
155        #[cfg(all(not(feature = "force-fallback"), $($cfg)*))]
156        #[allow(dead_code)]
157        impl AtomicMeta<$int> {
158            pub const HAS_ATOMIC: bool = true;
159        }
160
161        #[cfg(all(not(feature = "force-fallback"), $($cfg)*))]
162        impl<Fallback> GetAtomicOrFallback
163            for AtomicOrFallback<$int, Fallback, true>
164        {
165            type Type = atomic::$atomic;
166        }
167    };
168}
169
170with_primitive_atomics!(impl_atomic_meta);
171
172#[allow(unused_macros)]
173macro_rules! define_primitive_atomic {
174    (
175        $atomic:ident$(<$generic:ident>)?,
176        $type:ty,
177        [$($cfg:tt)*],
178        $doc:expr
179    ) => {
180        #[cfg(all(not(doc), not(feature = "force-fallback"), $($cfg)*))]
181        pub type $atomic$(<$generic>)? = atomic::$atomic$(<$generic>)?;
182
183        #[cfg(any(doc, feature = "force-fallback", not($($cfg)*)))]
184        #[cfg_attr(feature = "doc_cfg", doc(cfg(feature = "primitives")))]
185        /// An atomic
186        #[doc = concat!("[`", stringify!($type), "`].")]
187        ///
188        /// This is either an alias to the type in [`core::sync::atomic`], or,
189        /// if not available[^1], a spinlock-based fallback type.
190        ///
191        /// [^1]: If an appropriate type exists in [`core::sync::atomic`], but
192        /// compare-and-swap operations are not provided for it (i.e., only
193        /// loads and stores are supported), it is still considered to be "not
194        /// available" for the purposes of this crate.
195        ///
196        #[doc = $doc]
197        pub type $atomic$(<$generic>)? = fallback::$atomic$(<$generic>)?;
198    };
199}
200
201#[cfg(feature = "primitives")]
202with_primitive_atomics!(define_primitive_atomic);
203
204#[cfg(feature = "primitives")]
205define_primitive_atomic!(
206    AtomicPtr<T>,
207    *mut T,
208    [target_has_atomic = "ptr"],
209    "[`*mut T`]: pointer"
210);
211
212#[cfg(feature = "primitives")]
213#[cfg_attr(feature = "doc_cfg", doc(cfg(feature = "primitives")))]
214/// An atomic [`bool`].
215///
216/// This type alias is provided for completeness, but it always points to the
217/// real [`AtomicBool`][real] in [`core::sync::atomic`], as even the fallback
218/// atomic implementation in this crate requires [`AtomicBool`][real].
219///
220/// [real]: atomic::AtomicBool
221pub type AtomicBool = atomic::AtomicBool;
222
223macro_rules! with_c_atomics {
224    ($macro:path) => {
225        #[cfg(feature = "c_char")]
226        $macro!(AtomicCChar, c_char, "c_char");
227        #[cfg(feature = "c_schar")]
228        $macro!(AtomicCSchar, c_schar, "c_schar");
229        #[cfg(feature = "c_uchar")]
230        $macro!(AtomicCUchar, c_uchar, "c_uchar");
231        #[cfg(feature = "c_short")]
232        $macro!(AtomicCShort, c_short, "c_short");
233        #[cfg(feature = "c_ushort")]
234        $macro!(AtomicCUshort, c_ushort, "c_ushort");
235        #[cfg(feature = "c_int")]
236        $macro!(AtomicCInt, c_int, "c_int");
237        #[cfg(feature = "c_uint")]
238        $macro!(AtomicCUint, c_uint, "c_uint");
239        #[cfg(feature = "c_long")]
240        $macro!(AtomicCLong, c_long, "c_long");
241        #[cfg(feature = "c_ulong")]
242        $macro!(AtomicCUlong, c_ulong, "c_ulong");
243        #[cfg(feature = "c_longlong")]
244        $macro!(AtomicCLonglong, c_longlong, "c_longlong");
245        #[cfg(feature = "c_ulonglong")]
246        $macro!(AtomicCUlonglong, c_ulonglong, "c_ulonglong");
247    };
248}
249
250#[allow(unused_macros)]
251macro_rules! alias_c_type {
252    ($atomic:ident, $int:ident, $($x:tt)*) => {
253        #[allow(clippy::incompatible_msrv)] // see note in Cargo.toml
254        #[allow(non_camel_case_types)]
255        pub type $int = super::ffi::$int;
256    };
257}
258
259mod c_types {
260    with_c_atomics!(alias_c_type);
261}
262
263#[allow(unused_macros)]
264macro_rules! define_c_atomic {
265    ($atomic:ident, $int:ident, $feature:literal) => {
266        #[cfg(not(doc))]
267        pub type $atomic = <AtomicOrFallback<
268            c_types::$int,
269            fallback::$atomic,
270            { AtomicMeta::<c_types::$int>::HAS_ATOMIC },
271        > as GetAtomicOrFallback>::Type;
272
273        #[cfg(doc)]
274        #[cfg_attr(feature = "doc_cfg", doc(cfg(feature = $feature)))]
275        /// An atomic
276        #[doc = concat!("[`", stringify!($int), "`][1].")]
277        ///
278        /// This is either an alias to the appropriate atomic integer type in
279        /// [`core::sync::atomic`], or a spinlock-based fallback type.
280        ///
281        #[doc = concat!("[1]: ffi::", stringify!($int))]
282        pub type $atomic = fallback::$atomic;
283    };
284}
285
286with_c_atomics!(define_c_atomic);
287
288mod fallback;
289
290#[cfg(doc)]
291#[cfg_attr(feature = "doc_cfg", doc(cfg(doc)))]
292/// An example fallback implementation of an atomic integer.
293///
294/// When no built-in atomic for a certain integer type is available, its type
295/// alias in this crate points to a type like this, except with methods that
296/// take and return that integer type, rather than [`i32`].
297///
298/// This type internally uses spinlocks, which can cause deadlocks with signal
299/// handlers. To avoid this, enable the feature `signal`, which blocks incoming
300/// signals while the spinlock is held.
301///
302/// The API of this type is designed to be compatible with the atomic integer
303/// types in [`core::sync::atomic`].
304///
305/// This type is exposed only in the documentation for illustrative purposes.
306pub use fallback::AtomicFallback;
307
308#[cfg(doc)]
309#[cfg_attr(feature = "doc_cfg", doc(cfg(doc)))]
310/// An example fallback implementation of an atomic pointer.
311///
312/// [`AtomicPtr`] points to a type like this when no built-in atomic pointer is
313/// available.
314///
315/// This type is the pointer version of [`AtomicFallback`]; see its
316/// documentation for more details. Like [`AtomicFallback`], this type is
317/// exposed only in the documentation for illustrative purposes.
318pub use fallback::AtomicFallbackPtr;