facet_poke/
struct_.rs

1use core::ptr::NonNull;
2use facet_trait::{FieldError, Opaque, OpaqueConst, OpaqueUninit, Shape, ShapeExt as _, StructDef};
3
4use super::{Guard, ISet, PokeValue};
5
6/// Allows poking a struct (setting fields, etc.)
7pub struct PokeStruct<'mem> {
8    data: OpaqueUninit<'mem>,
9    shape: &'static Shape,
10    def: StructDef,
11    iset: ISet,
12}
13
14impl<'mem> PokeStruct<'mem> {
15    #[inline(always)]
16    /// Coerce back into a `PokeValue`
17    pub fn into_value(self) -> PokeValue<'mem> {
18        unsafe { PokeValue::new(self.data, self.shape) }
19    }
20
21    /// Shape getter
22    #[inline(always)]
23    pub fn shape(&self) -> &'static Shape {
24        self.shape
25    }
26    /// Creates a new PokeStruct
27    ///
28    /// # Safety
29    ///
30    /// The `data`, `shape`, and `def` must match
31    pub unsafe fn new(data: OpaqueUninit<'mem>, shape: &'static Shape, def: StructDef) -> Self {
32        Self {
33            data,
34            iset: Default::default(),
35            shape,
36            def,
37        }
38    }
39
40    /// Checks if all fields in the struct have been initialized.
41    /// Panics if any field is not initialized, providing details about the uninitialized field.
42    pub fn assert_all_fields_initialized(&self) {
43        for (i, field) in self.def.fields.iter().enumerate() {
44            if !self.iset.has(i) {
45                panic!(
46                    "Field '{}' was not initialized. Complete schema:\n{:?}",
47                    field.name, self.shape
48                );
49            }
50        }
51    }
52
53    /// Asserts that every field has been initialized and forgets the PokeStruct.
54    ///
55    /// This method is only used when the origin is borrowed.
56    /// If this method is not called, all fields will be freed when the PokeStruct is dropped.
57    ///
58    /// # Panics
59    ///
60    /// This function will panic if any field is not initialized.
61    pub fn build_in_place(self) -> Opaque<'mem> {
62        // ensure all fields are initialized
63        self.assert_all_fields_initialized();
64
65        let data = unsafe { self.data.assume_init() };
66
67        // prevent field drops when the PokeStruct is dropped
68        core::mem::forget(self);
69
70        data
71    }
72
73    /// Builds a value of type `T` from the PokeStruct, then deallocates the memory
74    /// that this PokeStruct was pointing to.
75    ///
76    /// # Panics
77    ///
78    /// This function will panic if:
79    /// - Not all the fields have been initialized.
80    /// - The generic type parameter T does not match the shape that this PokeStruct is building.
81    pub fn build<T: crate::Facet>(self, guard: Option<Guard>) -> T {
82        let mut guard = guard;
83        let this = self;
84        // this changes drop order: guard must be dropped _after_ this.
85
86        this.assert_all_fields_initialized();
87        this.shape.assert_type::<T>();
88        if let Some(guard) = &guard {
89            guard.shape.assert_type::<T>();
90        }
91
92        let result = unsafe {
93            let ptr = this.data.as_mut_ptr() as *const T;
94            core::ptr::read(ptr)
95        };
96        guard.take(); // dealloc
97        core::mem::forget(this);
98        result
99    }
100
101    /// Build that PokeStruct into a boxed completed shape.
102    ///
103    /// # Panics
104    ///
105    /// This function will panic if:
106    /// - Not all the fields have been initialized.
107    /// - The generic type parameter T does not match the shape that this PokeStruct is building.
108    pub fn build_boxed<T: crate::Facet>(self) -> Box<T> {
109        self.assert_all_fields_initialized();
110        self.shape.assert_type::<T>();
111
112        let boxed = unsafe { Box::from_raw(self.data.as_mut_ptr() as *mut T) };
113        core::mem::forget(self);
114        boxed
115    }
116
117    /// Moves the contents of this `PokeStruct` into a target memory location.
118    ///
119    /// # Safety
120    ///
121    /// The target pointer must be valid and properly aligned,
122    /// and must be large enough to hold the value.
123    /// The caller is responsible for ensuring that the target memory is properly deallocated
124    /// when it's no longer needed.
125    pub unsafe fn move_into(self, target: NonNull<u8>, guard: Option<Guard>) {
126        self.assert_all_fields_initialized();
127        if let Some(guard) = &guard {
128            guard.shape.assert_shape(self.shape);
129        }
130
131        unsafe {
132            core::ptr::copy_nonoverlapping(
133                self.data.as_mut_ptr(),
134                target.as_ptr(),
135                self.shape.layout.size(),
136            );
137        }
138        core::mem::forget(self);
139    }
140
141    /// Gets a field, by name
142    pub fn field_by_name(&self, name: &str) -> Result<(usize, crate::Poke<'mem>), FieldError> {
143        let index = self
144            .def
145            .fields
146            .iter()
147            .position(|f| f.name == name)
148            .ok_or(FieldError::NoSuchStaticField)?;
149        Ok((index, self.field(index)?))
150    }
151
152    /// Get a field writer for a field by index.
153    ///
154    /// # Errors
155    ///
156    /// Returns an error if:
157    /// - The shape doesn't represent a struct.
158    /// - The index is out of bounds.
159    pub fn field(&self, index: usize) -> Result<crate::Poke<'mem>, FieldError> {
160        if index >= self.def.fields.len() {
161            return Err(FieldError::IndexOutOfBounds);
162        }
163
164        let field = &self.def.fields[index];
165
166        // Get the field's address
167        let field_addr = unsafe { self.data.field_uninit(field.offset) };
168        let field_shape = field.shape;
169
170        let poke = unsafe { crate::Poke::unchecked_new(field_addr, field_shape) };
171        Ok(poke)
172    }
173
174    /// Sets a field's value by its index.
175    ///
176    /// # Errors
177    ///
178    /// Returns an error if:
179    /// - The index is out of bounds
180    /// - The field shapes don't match
181    pub fn set(&mut self, index: usize, value: OpaqueConst) -> Result<(), FieldError> {
182        if index >= self.def.fields.len() {
183            return Err(FieldError::IndexOutOfBounds);
184        }
185        let field = &self.def.fields[index];
186        let field_shape = field.shape;
187
188        unsafe {
189            core::ptr::copy_nonoverlapping(
190                value.as_ptr(),
191                self.data.field_uninit(field.offset).as_mut_ptr(),
192                field_shape.layout.size(),
193            );
194            self.iset.set(index);
195        }
196
197        Ok(())
198    }
199
200    /// Sets a field's value by its name.
201    ///
202    /// # Errors
203    ///
204    /// Returns an error if:
205    /// - The field name doesn't exist
206    /// - The field shapes don't match
207    pub fn set_by_name(&mut self, name: &str, value: OpaqueConst) -> Result<(), FieldError> {
208        let index = self
209            .def
210            .fields
211            .iter()
212            .position(|f| f.name == name)
213            .ok_or(FieldError::NoSuchStaticField)?;
214        self.set(index, value)
215    }
216
217    /// Marks a field as initialized.
218    ///
219    /// # Safety
220    ///
221    /// The caller must ensure that the field is initialized. Only call this after writing to
222    /// an address gotten through [`Self::field`] or [`Self::field_by_name`].
223    pub unsafe fn mark_initialized(&mut self, index: usize) {
224        self.iset.set(index);
225    }
226
227    /// Gets the struct definition
228    pub fn def(&self) -> StructDef {
229        self.def
230    }
231}
232
233impl Drop for PokeStruct<'_> {
234    fn drop(&mut self) {
235        self.def
236            .fields
237            .iter()
238            .enumerate()
239            .filter_map(|(i, field)| {
240                if self.iset.has(i) {
241                    Some((field, field.shape.vtable.drop_in_place?))
242                } else {
243                    None
244                }
245            })
246            .for_each(|(field, drop_fn)| unsafe {
247                drop_fn(self.data.field_init(field.offset));
248            });
249    }
250}