toolbox_rs/
unsafe_slice.rs

1use std::cell::UnsafeCell;
2
3/// This struct serves as a wrapper around unsafe accesses to a vector of
4/// elements.
5///
6/// This can be used if (and only if) we know (and the compiler doesn't) that
7/// threads do not access the same index during the concurrent processing.
8///
9/// DO NOT REUSE IN YOUR PROJECTS!
10///
11#[derive(Copy, Clone)]
12pub struct UnsafeSlice<'a, T> {
13    slice: &'a [UnsafeCell<T>],
14}
15unsafe impl<T: Send + Sync> Send for UnsafeSlice<'_, T> {}
16unsafe impl<T: Send + Sync> Sync for UnsafeSlice<'_, T> {}
17
18impl<'a, T> UnsafeSlice<'a, T> {
19    pub fn new(slice: &'a mut [T]) -> Self {
20        let ptr = slice as *mut [T] as *const [UnsafeCell<T>];
21        Self {
22            slice: unsafe { &*ptr },
23        }
24    }
25
26    ///  # Safety
27    ///  Two threads concurrently writing to the same location will cause UB!!
28    #[allow(clippy::mut_from_ref)]
29    pub unsafe fn get_mut(&self, index: usize) -> &mut T {
30        unsafe { &mut *self.slice[index].get() }
31    }
32
33    /// Returns a shared reference to the element at the given index.
34    ///
35    /// This function is safe to call because it returns an immutable reference,
36    /// which can be shared between multiple threads. However, it's important to note
37    /// that this safety relies on the exclusive access guarantee provided by the
38    /// mutable reference passed to `UnsafeSlice::new()`.
39    ///
40    /// # Arguments
41    ///
42    /// * `index` - Position of the element to access
43    ///
44    /// # Returns
45    ///
46    /// Reference to the element at `index`
47    ///
48    /// # Panics
49    ///
50    /// Panics if `index` is out of bounds
51    ///
52    /// # Examples
53    ///
54    /// ```
55    /// # use toolbox_rs::unsafe_slice::UnsafeSlice;
56    /// let mut data = vec![1, 2, 3];
57    /// let slice = UnsafeSlice::new(&mut data);
58    /// assert_eq!(*slice.get(0), 1);
59    /// ```
60    pub fn get(&self, index: usize) -> &T {
61        unsafe { &mut *self.slice[index].get() }
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::UnsafeSlice;
68
69    #[test]
70    fn instantiate() {
71        let mut data = vec![0, 1, 23, 83, 38, 3, 8947, 2762];
72        let slice = UnsafeSlice::new(&mut data);
73        assert_eq!(*slice.get(0), 0);
74        assert_eq!(*slice.get(1), 1);
75        assert_eq!(*slice.get(2), 23);
76        assert_eq!(*slice.get(3), 83);
77        assert_eq!(*slice.get(4), 38);
78        assert_eq!(*slice.get(5), 3);
79        assert_eq!(*slice.get(6), 8947);
80        assert_eq!(*slice.get(7), 2762);
81    }
82
83    #[test]
84    fn test_get_mut() {
85        let mut data = vec![1, 2, 3, 4];
86        let slice = UnsafeSlice::new(&mut data);
87
88        // SAFETY: We're only accessing each index once
89        // and not sharing the slice between threads
90        unsafe {
91            *slice.get_mut(0) = 10;
92            *slice.get_mut(2) = 30;
93        }
94
95        assert_eq!(*slice.get(0), 10);
96        assert_eq!(*slice.get(1), 2);
97        assert_eq!(*slice.get(2), 30);
98        assert_eq!(*slice.get(3), 4);
99
100        // Verify we can read the modified values
101        assert_eq!(data[0], 10);
102        assert_eq!(data[2], 30);
103    }
104}