ghost_gc/
write.rs

1#[cfg(doc)]
2use crate::Gc;
3
4use crate::locked::Unlock;
5
6/// A marker type which indicates that any owning [`Gc`] has been marked as having been modified.
7#[repr(transparent)]
8pub struct Write<T: ?Sized>(T);
9
10impl<T: ?Sized> Write<T> {
11    /// # Safety
12    /// The parent [`Gc`] must have been marked as mutated.
13    pub unsafe fn new_unchecked(value: &T) -> &Write<T> {
14        // Safety: Write is a thin wrapper around `T`.
15        unsafe { std::mem::transmute(value) }
16    }
17
18    pub fn new_static(value: &T) -> &Write<T>
19    where
20        T: 'static,
21    {
22        unsafe { Write::new_unchecked(value) }
23    }
24
25    pub fn into_inner(&self) -> &T {
26        &self.0
27    }
28
29    pub fn unlock(&self) -> &T::Unlocked
30    where
31        T: Unlock,
32    {
33        unsafe { self.0.unlock_unchecked() }
34    }
35
36    /// Projects a write permission into a write permision of the of the values contained by
37    /// `self`.
38    ///
39    /// # Panics
40    /// When the closure returns a reference to a value not contained within the bounds of `self`.
41    pub fn project<U: ?Sized>(&self, f: impl for<'a> FnOnce(&'a T) -> &'a U) -> &Write<U> {
42        self.try_project(f).unwrap()
43    }
44
45    /// Projects a write permission into a write permision of the of the values contained by
46    /// `self`, returning an error if the closure returns a reference to a value not contained
47    /// within the bounds of `self`.
48    pub fn try_project<U: ?Sized>(
49        &self,
50        f: impl for<'a> FnOnce(&'a T) -> &'a U,
51    ) -> Result<&Write<U>, WriteProjectError> {
52        let size = size_of_val(self) as isize;
53        let self_addr = (self as *const Write<T>).addr() as isize;
54        let proj = f(&self.0);
55        let proj_addr = (proj as *const U).addr() as isize;
56
57        if (0..size).contains(&(proj_addr - self_addr)) {
58            unsafe { Ok(Write::new_unchecked(proj)) }
59        } else {
60            Err(WriteProjectError)
61        }
62    }
63
64    /// Projects a write permission into a write permission of one of the containing objects fields.
65    ///
66    /// # Safety
67    /// The given closure must return a reference to a value which is owned by self. The closure
68    /// *must not* dereference a [`Gc`], or in any way project into a value which is owned
69    /// by another garbage collected pointer, and which could itself contain a garbage collected
70    /// pointer.
71    ///
72    /// # Examples
73    /// ```
74    /// # use ghost_gc::{locked::LockedCell, Gc, once_arena, Collect, Collector};
75    /// # once_arena(|mt| {
76    /// #
77    /// # unsafe impl<T: Collect> Collect for LinkedList<'_, T> {
78    /// #   const NEEDS_TRACE: bool = T::NEEDS_TRACE;
79    /// #
80    /// #   fn trace(&self, c: &Collector) {
81    /// #       self.data.trace(c);
82    /// #       match self.next.get() {
83    /// #           Some(v) => v.trace(c),
84    /// #           None => {}
85    /// #       }
86    /// #   }
87    /// # }
88    /// #
89    /// #[derive(Debug)]
90    /// struct LinkedList<'b, T> {
91    ///     data: T,
92    ///     next: LockedCell<Option<Gc<'b, Self>>>,
93    /// }
94    ///
95    /// let head = Gc::new(LinkedList::<'_, u32> {
96    ///     data: 0,
97    ///     next: LockedCell::new(Some(Gc::new(LinkedList {
98    ///         data: 1,
99    ///         next: LockedCell::new(None)
100    ///     }, mt)))
101    /// }, mt);
102    ///
103    /// unsafe {
104    ///     head.write().project_unchecked(|x| &x.data);
105    ///     head.write().project_unchecked(|x| &x.next);
106    /// }
107    /// # });
108    /// ```
109    pub unsafe fn project_unchecked<U: ?Sized>(
110        &self,
111        f: impl for<'a> FnOnce(&'a T) -> &'a U,
112    ) -> &Write<U> {
113        let self_ref: &T = &self.0;
114
115        let proj = f(self_ref);
116
117        unsafe { Write::new_unchecked(proj) }
118    }
119}
120
121#[derive(Debug)]
122pub struct WriteProjectError;
123
124#[cfg(test)]
125mod tests {
126    use crate::Write;
127
128    #[test]
129    fn basic_projection() {
130        struct Test {
131            a: u32,
132            b: &'static str,
133        }
134
135        let t = Test {
136            a: 17,
137            b: "Hello, World!",
138        };
139
140        let w = Write::new_static(&t);
141
142        let _: &Write<u32> = w.project(|f| &f.a);
143        let _: &Write<&str> = w.project(|f| &f.b);
144    }
145
146    #[test]
147    #[should_panic]
148    fn incorrect_projection() {
149        struct Test {
150            _a: &'static str,
151        }
152
153        let t = Test {
154            _a: "Hello, World!",
155        };
156
157        let w = Write::new_static(&t);
158
159        let _ = w.project(|_| "Some other string.");
160    }
161}