Skip to main content

thin_status/
thin_arc_or_int.rs

1// Copyright 2026 <https://github.com/ppetr/>
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14use std::cmp::Ordering;
15use std::ffi::c_void;
16use std::fmt;
17use std::hash::{Hash, Hasher};
18use std::marker::PhantomData;
19use std::ptr::NonNull;
20use triomphe::ThinArc;
21
22/// Stores an `isize` as a tagged value inside a pointer. This means that one bit of `isize` isn't
23/// available, and therefore only numbers within `IsizeInPtr::MIN` and `IsizeIntPtr::MAX` are
24/// convertible.
25///
26/// To wrap `isize` (or any other numerical `i..` type) into `IsizeInPtr` (if it fits), use
27/// `try_from` from `impl TryFrom<isize> for IsizeInPtr`.
28#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Debug)]
29pub struct IsizeInPtr {
30    /// As all numerical values are tagged by `TAG_MASK`, this is always non-zero.
31    ptr: NonNull<c_void>,
32}
33
34impl IsizeInPtr {
35    /// Maximal value that can be stored.
36    pub const MAX: isize = isize::MAX >> 1;
37    pub const MIN: isize = isize::MIN >> 1;
38    const TAG_MASK: isize = 1;
39
40    fn from_ptr(ptr: *const c_void) -> Option<Self> {
41        if (ptr as isize & Self::TAG_MASK) == 0 {
42            None
43        } else {
44            Some(unsafe { Self::new_unchecked(ptr) })
45        }
46    }
47
48    fn from_isize_unchecked(value: isize) -> Self {
49        let tagged = (value << 1) | Self::TAG_MASK;
50        unsafe { Self::new_unchecked(tagged as *const c_void) }
51    }
52
53    unsafe fn new_unchecked(ptr: *const c_void) -> Self {
54        IsizeInPtr {
55            ptr: unsafe { NonNull::from_ref(&*ptr) },
56        }
57    }
58
59    /// Returns the wrapped value.
60    pub fn get(&self) -> isize {
61        (self.ptr.as_ptr() as isize) >> 1
62    }
63}
64
65impl Default for IsizeInPtr {
66    fn default() -> Self {
67        Self::from_isize_unchecked(0)
68    }
69}
70
71impl From<IsizeInPtr> for isize {
72    /// Convert `value` back to `isize`.
73    fn from(value: IsizeInPtr) -> isize {
74        value.get()
75    }
76}
77
78impl TryFrom<isize> for IsizeInPtr {
79    type Error = TryFromIsizeError<isize>;
80
81    /// Wrap a `value` if it fits inside `IsizeInPtr`.
82    fn try_from(value: isize) -> Result<IsizeInPtr, Self::Error> {
83        if (value <= Self::MAX) && (value >= Self::MIN) {
84            Ok(Self::from_isize_unchecked(value))
85        } else {
86            Err(TryFromIsizeError { original: value })
87        }
88    }
89}
90
91macro_rules! impl_try_from_for_integral {
92    ($($t:ty),*) => {
93        $(
94            impl TryFrom<$t> for IsizeInPtr {
95                type Error = TryFromIsizeError<$t>;
96
97                fn try_from(value: $t) -> Result<Self, Self::Error> {
98                    isize::try_from(value).ok()
99                        .and_then(|n: isize| IsizeInPtr::try_from(n).ok())
100                        .ok_or(TryFromIsizeError{ original: value })
101                }
102            }
103        )*
104    };
105}
106impl_try_from_for_integral!(i8, i16, i32, i64, i128);
107
108/// Returned when a value is out of the range of `IsizeError`.
109#[derive(Debug, Clone, Copy)]
110pub struct TryFromIsizeError<N> {
111    pub original: N,
112}
113
114impl<N: std::fmt::Display> std::fmt::Display for TryFromIsizeError<N> {
115    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
116        write!(
117            f,
118            "Value {} doesn't fit inside taggeed isize bounds [{}, {}]",
119            self.original,
120            IsizeInPtr::MIN,
121            IsizeInPtr::MAX
122        )
123    }
124}
125
126impl<N: std::fmt::Display + std::fmt::Debug> std::error::Error for TryFromIsizeError<N> {}
127
128/// A type representing either a signed pointer-sized integer (`isize`) or
129/// a reference-counted pointer (`ThinArc<H, T>`).
130///
131/// Optimized using `NonNull` so that `Option<ThinArcOrInt<H, T>>` takes up exactly
132/// the size of a single architecture pointer.
133pub struct ThinArcOrInt<H, T> {
134    /// As all numerical values are tagged by `TAG_MASK`, this is always non-zero.
135    raw: NonNull<c_void>,
136    _marker: PhantomData<ThinArc<H, T>>,
137}
138
139unsafe impl<H, T> Send for ThinArcOrInt<H, T> where ThinArc<H, T>: Send {}
140unsafe impl<H, T> Sync for ThinArcOrInt<H, T> where ThinArc<H, T>: Sync {}
141
142impl<H, T> ThinArcOrInt<H, T> {
143    /// Constructs an instance from a signed integer that will be stored as a tagged value inside
144    /// this pointer.
145    pub fn from_isize(val: IsizeInPtr) -> Self {
146        Self {
147            raw: val.ptr,
148            _marker: PhantomData,
149        }
150    }
151
152    /// Constructs an instance from a ThinArc pointer.
153    /// (Assumes the pointer is aligned and its LSB is 0.)
154    pub fn from_arc(arc: ThinArc<H, T>) -> Self {
155        let ptr = ThinArc::into_raw(arc);
156        debug_assert!(
157            IsizeInPtr::from_ptr(ptr).is_none(),
158            "Pointer must be 2-aligned!"
159        );
160        // Safety: ThinArc allocations on the heap are never null.
161        Self {
162            raw: unsafe { NonNull::from_ref(&*ptr) },
163            _marker: PhantomData,
164        }
165    }
166
167    /// If `slice` is empty, tries to convert a `value` using `try_into()` to `IsizeInPtr`. If it
168    /// succeeds, stores it as a tagged integer inside this pointer. Otherwise constructs a
169    /// `ThinArc` to hold everything.
170    pub fn from_convertible<U, E>(value: U, slice: &[T]) -> Self
171    where
172        U: TryInto<IsizeInPtr, Error = E> + Into<H>,
173        E: Into<H>,
174        T: Copy,
175    {
176        if slice.is_empty() {
177            match value.try_into() {
178                Ok(i) => Self::from_isize(i),
179                Err(e) => {
180                    Self::from_arc(ThinArc::from_header_and_iter(e.into(), std::iter::empty()))
181                }
182            }
183        } else {
184            Self::from_arc(ThinArc::from_header_and_slice(value.into(), slice))
185        }
186    }
187
188    /// Returns `true` iff this instance holds a number as a tagged value inside this pointer, that
189    /// is, without any memory allocation.
190    pub fn has_number(&self) -> bool {
191        self.as_isize().is_some()
192    }
193
194    /// Returns `true` iff this instance holds real pointer to a `ThinArc<H, T>` value.
195    pub fn has_ref(&self) -> bool {
196        !self.has_number()
197    }
198
199    /// Returns the tagged integer value inside this pointer if present, or `None` otherwise.
200    pub fn as_isize(&self) -> Option<isize> {
201        IsizeInPtr::from_ptr(self.raw.as_ptr()).map(|i| i.into())
202    }
203
204    /// Returns a shared reference to a `ThinArc<H, T>` if present, or `None` otherwise.
205    pub fn as_arc(&self) -> Option<&ThinArc<H, T>> {
206        if self.has_ref() {
207            unsafe { Some(self.as_arc_internal()) }
208        } else {
209            None
210        }
211    }
212
213    unsafe fn as_arc_internal(&self) -> &ThinArc<H, T> {
214        &*(&self.raw as *const NonNull<c_void> as *const ThinArc<H, T>)
215    }
216}
217
218impl<H, T> Default for ThinArcOrInt<H, T> {
219    fn default() -> Self {
220        Self::from_isize(Default::default())
221    }
222}
223
224impl<H, T> Drop for ThinArcOrInt<H, T> {
225    fn drop(&mut self) {
226        if self.has_ref() {
227            let _arc = unsafe { ThinArc::<H, T>::from_raw(self.raw.as_ptr()) };
228        }
229    }
230}
231
232impl<H, T> Clone for ThinArcOrInt<H, T> {
233    fn clone(&self) -> Self {
234        if self.has_number() {
235            Self {
236                raw: self.raw,
237                _marker: PhantomData,
238            }
239        } else {
240            let arc = unsafe { self.as_arc_internal() };
241            let cloned_arc = arc.clone();
242            Self::from_arc(cloned_arc)
243        }
244    }
245}
246
247impl<H, T> PartialEq for ThinArcOrInt<H, T>
248where
249    H: PartialEq,
250    T: PartialEq,
251{
252    fn eq(&self, other: &Self) -> bool {
253        match (self.as_isize(), other.as_isize()) {
254            (Some(s), Some(o)) => s == o,
255            (None, None) => unsafe { self.as_arc_internal() == other.as_arc_internal() },
256            _ => false,
257        }
258    }
259}
260
261impl<H, T> Eq for ThinArcOrInt<H, T>
262where
263    H: Eq,
264    T: Eq,
265{
266}
267
268impl<H, T> PartialOrd for ThinArcOrInt<H, T>
269where
270    H: PartialOrd,
271    T: PartialOrd,
272{
273    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
274        match (self.as_isize(), other.as_isize()) {
275            (Some(s), Some(o)) => s.partial_cmp(&o),
276            (None, None) => unsafe { self.as_arc_internal().partial_cmp(other.as_arc_internal()) },
277            (Some(_), None) => Some(Ordering::Less),
278            (None, Some(_)) => Some(Ordering::Greater),
279        }
280    }
281}
282
283impl<H, T> Ord for ThinArcOrInt<H, T>
284where
285    H: Ord,
286    T: Ord,
287{
288    fn cmp(&self, other: &Self) -> Ordering {
289        self.partial_cmp(other)
290            .expect("ThinArc::partial_cmp returned `None`")
291    }
292}
293
294impl<H, T> Hash for ThinArcOrInt<H, T>
295where
296    H: Hash,
297    T: Hash,
298{
299    fn hash<S: Hasher>(&self, state: &mut S) {
300        if let Some(num) = self.as_isize() {
301            0.hash(state);
302            num.hash(state);
303        } else {
304            1.hash(state);
305            unsafe { self.as_arc_internal().hash(state) };
306        }
307    }
308}
309
310impl<H: fmt::Debug, T: fmt::Debug> fmt::Debug for ThinArcOrInt<H, T> {
311    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
312        if let Some(num) = self.as_isize() {
313            f.debug_tuple("ThinArcOrInt::Number").field(&num).finish()
314        } else {
315            f.debug_tuple("ThinArcOrInt::ThinArc")
316                .field(self.as_arc().unwrap())
317                .finish()
318        }
319    }
320}
321
322#[cfg(test)]
323mod tests {
324    use super::*;
325
326    fn from_isize(value: isize) -> ThinArcOrInt<(), ()> {
327        ThinArcOrInt::<(), ()>::from_isize(
328            IsizeInPtr::try_from(value).expect("Out of allowed bounds"),
329        )
330    }
331
332    #[test]
333    fn test_size() {
334        assert_eq!(
335            std::mem::size_of::<ThinArcOrInt<(), String>>(),
336            std::mem::size_of::<usize>()
337        );
338    }
339
340    #[test]
341    fn test_clone_and_drop() {
342        let arc = ThinArc::from_header_and_slice((), "Shared data".as_bytes());
343        let val1 = ThinArcOrInt::from_arc(arc);
344
345        let val2 = val1.clone();
346
347        assert_eq!(&val1.as_arc().unwrap().slice, "Shared data".as_bytes());
348        assert!(std::ptr::eq(
349            &val1.as_arc().unwrap().slice,
350            &val2.as_arc().unwrap().slice
351        ));
352    }
353
354    #[test]
355    fn test_option_size_optimization() {
356        assert_eq!(
357            std::mem::size_of::<ThinArcOrInt<(), String>>(),
358            std::mem::size_of::<usize>()
359        );
360        assert_eq!(
361            std::mem::size_of::<Option<ThinArcOrInt<(), String>>>(),
362            std::mem::size_of::<usize>()
363        );
364        assert_ne!(None, Some(from_isize(0)));
365    }
366
367    #[test]
368    fn test_negative_numbers() {
369        let negative = from_isize(-42);
370        assert!(negative.has_number());
371        assert_eq!(negative.as_isize(), Some(-42));
372    }
373
374    #[test]
375    fn test_max_number() {
376        let negative = from_isize(IsizeInPtr::MAX);
377        assert!(negative.has_number());
378        assert_eq!(negative.as_isize(), Some(IsizeInPtr::MAX));
379    }
380
381    #[test]
382    fn test_min_number() {
383        let negative = from_isize(IsizeInPtr::MIN);
384        assert!(negative.has_number());
385        assert_eq!(negative.as_isize(), Some(IsizeInPtr::MIN));
386    }
387
388    #[test]
389    fn test_thin_arc() {
390        let arc = ThinArc::from_header_and_slice((), b"Hello Rust");
391        let val = ThinArcOrInt::from_arc(arc);
392
393        assert!(val.has_ref());
394        assert!(!val.has_number());
395        assert_eq!(
396            &val.as_arc().expect("Must be Arc, not isize").slice,
397            b"Hello Rust"
398        );
399    }
400}