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}