pointer_value_pair/
pair.rs

1use std::{marker::PhantomData, mem};
2
3/// A pair consisting of a raw pointer (`*const T`) and an integer value, packed so that it takes the size of a pointer.
4///
5/// It is implemented by packing the integer value in the low bits of the pointer that are known to be
6/// zero because of alignment constraints.
7///
8/// The size of the value that can be stored alongside the pointer is 3 bits for most types, but ultimately depends on the minimum alignment of `T`:
9/// for example, if `mem::align_of::<T>() == 16` then 4 bits are available to store the value.
10///
11/// # Notes
12/// Pointers to zero-sized types do not have enough space to store any value, so it must be zero.
13#[repr(transparent)]
14#[derive(Debug)]
15pub struct PointerValuePair<T> {
16    repr: usize,
17    _phantom: PhantomData<T>,
18}
19
20impl<T> Copy for PointerValuePair<T> {}
21
22impl<T> Clone for PointerValuePair<T> {
23    fn clone(&self) -> Self {
24        PointerValuePair {
25            repr: self.repr,
26            _phantom: PhantomData,
27        }
28    }
29}
30
31const fn align_bits_mask<T>() -> usize {
32    mem::align_of::<T>() - 1
33}
34
35impl<T> PointerValuePair<T> {
36    /// Creates a new `PointerValuePair` from the given raw pointer and extra bits.
37    ///
38    /// # Panics
39    ///
40    /// Panics if the pointer type `*const T` does not have enough available low bits to store
41    /// the value.
42    pub fn new(ptr: *const T, value: usize) -> PointerValuePair<T> {
43
44        let m = align_bits_mask::<T>();
45        assert!(
46            value <= m,
47            "not enough alignment bits ({}) to store the value ({})",
48            Self::available_bits(),
49            value
50        );
51
52        let mut repr = ptr as usize;
53        repr |= value;
54
55        PointerValuePair {
56            repr,
57            _phantom: PhantomData,
58        }
59    }
60
61    /// Returns the number of bits available to store the value.
62    pub const fn available_bits() -> u32 {
63        align_bits_mask::<T>().count_ones()
64    }
65
66    /// Returns the maximum (inclusive) integer value that can be stored in the pointer.
67    pub const fn max_value() -> usize {
68        align_bits_mask::<T>()
69    }
70
71    /// Returns the pointer.
72    pub const fn ptr(self) -> *const T {
73        (self.repr & !align_bits_mask::<T>()) as *const T
74    }
75
76    /// Returns the value stored alongside the pointer.
77    pub const fn value(self) -> usize {
78        self.repr & align_bits_mask::<T>()
79    }
80}
81
82
83#[cfg(test)]
84mod tests {
85    use std::mem;
86    use super::PointerValuePair;
87
88    #[test]
89    fn pointer_sized() {
90        assert_eq!(mem::size_of::<*const i32>(), mem::size_of::<PointerValuePair<i32>>());
91    }
92
93    #[test]
94    fn basic_get_set() {
95        let pointee = 42usize;
96        let pv = PointerValuePair::new(&pointee, 3);
97        assert_eq!(pv.ptr(), &pointee as *const _);
98        let p_val = unsafe { *pv.ptr() };
99        assert_eq!(p_val, 42usize);
100        assert_eq!(pv.value(), 3);
101    }
102
103    #[test]
104    fn custom_alignments() {
105
106        #[repr(C, align(8))]
107        struct Align8(i32);
108        assert!(PointerValuePair::<Align8>::max_value() >= 0x7);
109        assert!(PointerValuePair::<Align8>::available_bits() >= 3);
110
111        #[repr(C, align(16))]
112        struct Align16(i32);
113        assert!(PointerValuePair::<Align16>::max_value() >= 0xF);
114        assert!(PointerValuePair::<Align16>::available_bits() >= 4);
115
116        #[repr(C, align(32))]
117        struct Align32(i32);
118        assert!(PointerValuePair::<Align32>::max_value() >= 0x1F);
119        assert!(PointerValuePair::<Align32>::available_bits() >= 5);
120    }
121}