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}