pac_cell/
lib.rs

1//! Provides [PacCell] (a cell of a parent and a child).
2
3use std::marker::PhantomPinned;
4use std::ptr::NonNull;
5use std::{cell::OnceCell, pin::Pin};
6
7/// A cell of a parent and a child, which is created by mutably borrowing the parent.
8/// While the parent is in the cell, it cannot be accessed in any way.
9/// Provides mutable access to the child.
10///
11/// This is useful in a rare case when you need to store and move both
12/// parent and their child together.
13///
14/// ## Examples
15///
16/// Basic usage:
17/// ```
18/// struct Hello {
19///     world: i64,
20/// }
21/// let hello = Hello { world: 10 };
22///
23/// let mut pac = pac_cell::PacCell::new(hello, |h| &mut h.world);
24///
25/// let initial = pac.with_mut(|world| {
26///     let i = **world;
27///     **world = 12;
28///     i
29/// });
30/// assert_eq!(initial, 10);
31///
32/// let hello_again = pac.unwrap();
33/// assert_eq!(hello_again.world, 12);
34/// ```
35///
36/// For a real-world-like example, see the crate tests.
37pub struct PacCell<P, C>(Pin<Box<PacInner<P, C>>>);
38
39/// Inner object of [Pac].
40///
41/// ## Safety
42///
43/// While this struct exist, the parent is considered mutably borrowed.
44/// Therefore, any access to parent is UB.
45///
46/// Because child might contain pointers to parent, this struct cannot
47/// be moved.
48struct PacInner<P, C> {
49    /// Child has to be defined before the parent, so it is dropped
50    /// before the parent
51    child: OnceCell<C>,
52    parent: P,
53
54    /// Mark this struct as non-movable. Not really needed, since we always
55    /// have it in `Pin<Box<_>>``, but there is no hard in being too explicit.
56    _pin: PhantomPinned,
57}
58
59impl<'p, P: 'p, C> PacCell<P, C> {
60    /// Creates Pac by moving the parent into a [Box] and then calling
61    /// the child constructor.
62    pub fn new<F>(parent: P, child_constructor: F) -> Self
63    where
64        F: FnOnce(&'p mut P) -> C,
65    {
66        Self::try_new::<_, ()>(parent, |p| Ok(child_constructor(p))).unwrap()
67    }
68
69    /// Creates Pac by moving the parent into a [Box] and then calling
70    /// the child constructor.
71    pub fn try_new<F, E>(parent: P, child_constructor: F) -> Result<Self, E>
72    where
73        F: FnOnce(&'p mut P) -> Result<C, E>,
74    {
75        // move engine into the struct and pin the struct on heap
76        let inner = PacInner {
77            parent,
78            child: OnceCell::new(),
79            _pin: PhantomPinned,
80        };
81        let mut inner = Box::pin(inner);
82
83        // create mut reference to engine, without borrowing the struct
84        // SAFETY: generally this would be unsafe, since one could obtain multiple mut refs this way.
85        //   But because we don't allow any access to engine, this mut reference is guaranteed
86        //   to be the only one.
87        let mut parent_ref = NonNull::from(&inner.as_mut().parent);
88        let parent_ref = unsafe { parent_ref.as_mut() };
89
90        // create fuel and move it into the struct
91        let child = child_constructor(parent_ref)?;
92        let _ = inner.child.set(child);
93
94        Ok(PacCell(inner))
95    }
96
97    /// Executes a function with a mutable reference to the child.
98    pub fn with_mut<F, R>(&mut self, f: F) -> R
99    where
100        F: FnOnce(&mut C) -> R,
101    {
102        let mut_ref: Pin<&mut PacInner<P, C>> = Pin::as_mut(&mut self.0);
103
104        // SAFETY: this is safe because we don't move the inner pinned object
105        let inner = unsafe { Pin::get_unchecked_mut(mut_ref) };
106        let fuel = inner.child.get_mut().unwrap();
107
108        f(fuel)
109    }
110
111    /// Drop the child and return the parent.
112    pub fn unwrap(self) -> P {
113        // SAFETY: this is safe because child is dropped when this function finishes,
114        //    but parent still exists.
115        let inner = unsafe { Pin::into_inner_unchecked(self.0) };
116        inner.parent
117    }
118}