Skip to main content

manual_share/
shared_box.rs

1//! # Manually shared box
2//! ```
3//! use std::thread;
4//! use manual_share::SharedBox;
5//!
6//! let b = Box::new(13);
7//! let mut b = SharedBox::from_box(b);
8//!
9//! let br = b.borrow();
10//! let j1 = thread::spawn(move || {
11//!     println!("{}", br.get());
12//!     br
13//! });
14//!
15//! let br = b.borrow();
16//! let j2 = thread::spawn(move || {
17//!     println!("{}", br.get() + 1);
18//!     br
19//! });
20//!
21//! b.try_return(j1.join().unwrap()).unwrap();
22//! b.try_return(j2.join().unwrap()).unwrap();
23//!
24//! let b = b.try_into_box().unwrap();
25//! println!("{:?}", b);
26//! ```
27
28/// A structure owning the original `Box`
29/// which can be used to create multiple `SharedBoxRef` to send to other thread.
30/// It uses a counter to record the number of `SharedBoxRef` that has been created and not given back.
31///
32/// Dropping `SharedBox` without returning all `SharedBoxRef` that it has created leaks its heap memory.
33/// When **`panic-on-drop`** feature is enabled, it will panic:
34/// ```should_panic
35/// let r = {
36///     let mut b = manual_share::SharedBox::new(0);
37///     b.borrow()
38///
39///     // dropping SharedBox, panic here
40/// };
41/// println!("{}", r.get());
42/// ```
43///
44/// When all `SharedBoxRef` have been returned back to `SharedBox`,
45/// it will free and properly release its heap memory when `SharedBox` is dropped.
46/// ```
47/// let mut b = manual_share::SharedBox::new(0);
48/// let r = b.borrow();
49/// b.try_return(r).unwrap();
50///
51/// // Dropping SharedBox here will free the heap memory it owns. No panic will occur.
52/// ```
53#[derive(Debug)]
54pub struct SharedBox<T: ?Sized> {
55    borrow_count: usize,
56    ptr: *mut T,
57}
58
59impl<T> SharedBox<T> {
60    /// Create a `SharedBox` by creating a `Box` first,
61    /// then convert it to a `SharedBox`.
62    pub fn new(value: T) -> Self {
63        let b = Box::new(value);
64        Self::from_box(b)
65    }
66}
67
68impl<T: ?Sized> SharedBox<T> {
69    /// Create a `SharedBox` by consuming a `Box`.
70    pub fn from_box(unique: Box<T>) -> Self {
71        Self {
72            borrow_count: 0,
73            ptr: Box::into_raw(unique),
74        }
75    }
76
77    /// Create a `SharedBoxRef` and increase the borrow count.
78    /// # panics
79    /// Panics when borrow count overflows `usize`.
80    pub fn borrow(&mut self) -> SharedBoxRef<T> {
81        self.borrow_count = self.borrow_count.checked_add(1).unwrap();
82        SharedBoxRef { ptr: self.ptr }
83    }
84
85    /// Try to return back the `SharedBoxRef`.
86    /// Returns `Err` if the `SharedBoxRef` does not originate from the same `SharedBox`.
87    ///
88    /// Decrease the borrow count if not error occurs.
89    ///
90    /// ```
91    /// use manual_share::SharedBox;
92    ///
93    /// let mut b1 = SharedBox::from_box(Box::new(8));
94    /// let r1 = b1.borrow();
95    /// b1.try_return(r1).unwrap();
96    ///
97    /// let mut b2 = SharedBox::from_box(Box::new(9));
98    /// let r2 = b2.borrow();
99    ///
100    /// // Giving SharedBoxRef to the wrong SharedBox returns Err.
101    /// let r2 = b1.try_return(r2).unwrap_err();
102    ///
103    /// b2.try_return(r2).unwrap();
104    /// ```
105    pub fn try_return(&mut self, reference: SharedBoxRef<T>) -> Result<(), SharedBoxRef<T>> {
106        if !core::ptr::eq(self.ptr, reference.ptr) {
107            return Err(reference);
108        }
109
110        if size_of_val(unsafe { &*self.ptr }) == 0 {
111            // ZST types can have multiple allocations to the same address, so we need to check for overflow.
112            if let Some(new_count) = self.borrow_count.checked_sub(1) {
113                self.borrow_count = new_count;
114                let _ = core::mem::ManuallyDrop::new(reference);
115                Ok(())
116            } else {
117                Err(reference)
118            }
119        } else {
120            self.borrow_count -= 1;
121            let _ = core::mem::ManuallyDrop::new(reference);
122            Ok(())
123        }
124    }
125
126    /// Try to convert `Self` into a `Box` if all borrowed `SharedBoxRef` has been given back.
127    ///
128    /// ```
129    /// use manual_share::SharedBox;
130    ///
131    /// let b = Box::new(0);
132    /// let mut b = SharedBox::from_box(b);
133    ///
134    /// let r = b.borrow();
135    ///
136    /// // Try to convert to Box without returning all SharedBoxRef returns Err.
137    /// let mut b = b.try_into_box().unwrap_err();
138    ///
139    /// b.try_return(r).unwrap();
140    ///
141    /// let b = b.try_into_box().unwrap();
142    /// assert_eq!(b, Box::new(0));
143    /// ```
144    pub fn try_into_box(self) -> Result<Box<T>, Self> {
145        if self.borrow_count > 0 {
146            Err(self)
147        } else {
148            let r = core::mem::ManuallyDrop::new(self);
149            Ok(unsafe { Box::from_raw(r.ptr) })
150        }
151    }
152    /// Directly get a reference to the value inside the `SharedBox`.
153    /// This use rust built-in lifetime check to ensure the reference is valid as long as the `SharedBox` is alive,
154    /// and has no runtime overhead.
155    pub fn get(&self) -> &T {
156        // SAFETY:
157        // The pointer is valid as long as the SharedBox is alive.
158        // All other references can only get immutable reference.
159        unsafe { &*self.ptr }
160    }
161}
162
163/// See https://users.rust-lang.org/t/built-a-crate-to-safely-share-box-and-vec-manually/141138/25?u=newdino for why it require `Sync`.
164unsafe impl<T: Send + Sync> Send for SharedBox<T> {}
165
166unsafe impl<T: Sync> Sync for SharedBox<T> {}
167
168impl<T: ?Sized> Drop for SharedBox<T> {
169    fn drop(&mut self) {
170        #[cfg(feature = "panic-on-drop")]
171        {
172            // Let user deal with other panics.
173            #[cfg(feature = "do-not-panic-when-panicking")]
174            if std::thread::panicking() {
175                return;
176            }
177
178            if self.borrow_count > 0 {
179                panic!("Dropping a SharedBox without giving back all SharedBoxRef")
180            }
181        }
182        // Only drops when there are no outstanding SharedBoxRef values to prevent use-after-free.
183        if self.borrow_count == 0 {
184            unsafe {
185                drop(Box::from_raw(self.ptr));
186            }
187        }
188    }
189}
190
191/// A Reference to `SharedBox` that can be sent to other threads.
192///
193/// Dropping a `SharedBoxRef` leaks the heap memory it points to.
194/// When **`panic-on-drop`** feature is enabled, dropping it will panic:
195/// ```should_panic
196/// let mut b = manual_share::SharedBox::new(1);
197/// b.borrow();
198///
199/// // forget SharedBox to make sure the panic is not caused by dropping it first.
200/// std::mem::forget(b);
201///
202/// // panic here due to dropping SharedBoxRef
203/// ```
204///
205/// Use `SharedBox::try_return` to consume it without causing panic.
206/// ```
207/// let mut b = manual_share::SharedBox::new(1);
208/// let r = b.borrow();
209/// b.try_return(r).unwrap();
210/// ```
211#[derive(Debug)]
212pub struct SharedBoxRef<T: ?Sized> {
213    ptr: *const T,
214}
215
216/// `SharedBoxRef` is like `&T`, which only requires `T: Sync` to implement `Send`.
217unsafe impl<T: Sync> Send for SharedBoxRef<T> {}
218unsafe impl<T: Sync> Sync for SharedBoxRef<T> {}
219
220impl<T: ?Sized> SharedBoxRef<T> {
221    /// Example usage:
222    /// ```
223    /// let mut b = manual_share::SharedBox::new(42);
224    /// let r = b.borrow();
225    /// let value = *r.get();
226    /// assert_eq!(value, 42);
227    ///
228    /// b.try_return(r).unwrap();
229    /// ```
230    ///
231    /// The reference got from this method has the same lifetime of the `SharedBoxRef`,
232    /// which means it will be invalidated after `SharedBoxRef` is given back to `SharedBox`:
233    /// ```compile_fail
234    /// let mut b = manual_share::SharedBox::new(42);
235    /// let br = b.borrow();
236    /// let r = br.get();
237    ///
238    /// b.try_return(br).unwrap();
239    ///
240    /// // r is no longer valid here.
241    /// println!("{}", r);
242    /// ```
243    pub fn get(&self) -> &T {
244        unsafe { &*self.ptr }
245    }
246}
247
248impl<T: ?Sized> Drop for SharedBoxRef<T> {
249    fn drop(&mut self) {
250        #[cfg(feature = "panic-on-drop")]
251        {
252            #[cfg(feature = "do-not-panic-when-panicking")]
253            // Let user deal with other panics.
254            if std::thread::panicking() {
255                return;
256            }
257
258            panic!("SharedBoxRef should not be dropped. Use SharedBox::try_return to consume it.");
259        }
260    }
261}
262
263#[cfg(test)]
264mod test {
265    use super::*;
266
267    #[test]
268    fn zst() {
269        let mut b1 = SharedBox::new(());
270        let mut b2 = SharedBox::new(());
271
272        let r11 = b1.borrow();
273        let r12 = b1.borrow();
274
275        let r2 = b2.borrow();
276
277        b1.try_return(r2).unwrap();
278
279        b1.try_return(r11).unwrap();
280        let r12 = b1.try_return(r12).unwrap_err();
281
282        b2.try_return(r12).unwrap();
283    }
284}