facet_poke/
struct_.rs

1use core::ptr::NonNull;
2use facet_core::{FieldError, Opaque, OpaqueConst, OpaqueUninit, Shape, StructDef};
3
4use super::{Guard, ISet, PokeValueUninit};
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) -> PokeValueUninit<'mem> {
18        unsafe { PokeValueUninit::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_bytes() 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) -> alloc::boxed::Box<T> {
109        self.assert_all_fields_initialized();
110        self.shape.assert_type::<T>();
111
112        let boxed = unsafe { alloc::boxed::Box::from_raw(self.data.as_mut_bytes() 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_bytes(),
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(
143        &self,
144        name: &str,
145    ) -> Result<(usize, crate::PokeUninit<'mem>), FieldError> {
146        let index = self
147            .def
148            .fields
149            .iter()
150            .position(|f| f.name == name)
151            .ok_or(FieldError::NoSuchStaticField)?;
152        Ok((index, self.field(index)?))
153    }
154
155    /// Get a field writer for a field by index.
156    ///
157    /// # Errors
158    ///
159    /// Returns an error if:
160    /// - The shape doesn't represent a struct.
161    /// - The index is out of bounds.
162    pub fn field(&self, index: usize) -> Result<crate::PokeUninit<'mem>, FieldError> {
163        if index >= self.def.fields.len() {
164            return Err(FieldError::IndexOutOfBounds);
165        }
166
167        let field = &self.def.fields[index];
168
169        // Get the field's address
170        let field_addr = unsafe { self.data.field_uninit(field.offset) };
171        let field_shape = field.shape;
172
173        let poke = unsafe { crate::PokeUninit::unchecked_new(field_addr, field_shape) };
174        Ok(poke)
175    }
176
177    /// Sets a field's value by its index, directly copying raw memory.
178    ///
179    /// # Safety
180    ///
181    /// This is unsafe because it directly copies memory without checking types.
182    /// The caller must ensure that `value` is of the correct type for the field.
183    ///
184    /// # Errors
185    ///
186    /// Returns an error if:
187    /// - The index is out of bounds
188    /// - The field shapes don't match
189    pub unsafe fn unchecked_set(
190        &mut self,
191        index: usize,
192        value: OpaqueConst,
193    ) -> Result<(), FieldError> {
194        if index >= self.def.fields.len() {
195            return Err(FieldError::IndexOutOfBounds);
196        }
197        let field = &self.def.fields[index];
198        let field_shape = field.shape;
199
200        unsafe {
201            core::ptr::copy_nonoverlapping(
202                value.as_ptr(),
203                self.data.field_uninit(field.offset).as_mut_bytes(),
204                field_shape.layout.size(),
205            );
206            self.iset.set(index);
207        }
208
209        Ok(())
210    }
211
212    /// Sets a field's value by its name, directly copying raw memory.
213    ///
214    /// # Safety
215    ///
216    /// This is unsafe because it directly copies memory without checking types.
217    /// The caller must ensure that `value` is of the correct type for the field.
218    ///
219    /// # Errors
220    ///
221    /// Returns an error if:
222    /// - The field name doesn't exist
223    /// - The field shapes don't match
224    pub unsafe fn unchecked_set_by_name(
225        &mut self,
226        name: &str,
227        value: OpaqueConst,
228    ) -> Result<(), FieldError> {
229        let index = self
230            .def
231            .fields
232            .iter()
233            .position(|f| f.name == name)
234            .ok_or(FieldError::NoSuchStaticField)?;
235        unsafe { self.unchecked_set(index, value) }
236    }
237
238    /// Sets a field's value by its index in a type-safe manner.
239    ///
240    /// This method takes ownership of the value and ensures proper memory management.
241    ///
242    /// # Errors
243    ///
244    /// Returns an error if:
245    /// - The index is out of bounds
246    /// - The field shapes don't match
247    pub fn set<T: crate::Facet>(&mut self, index: usize, value: T) -> Result<(), FieldError> {
248        let field_shape = self
249            .def
250            .fields
251            .get(index)
252            .ok_or(FieldError::IndexOutOfBounds)?
253            .shape;
254        field_shape.assert_type::<T>();
255
256        unsafe {
257            let opaque = OpaqueConst::new(&value);
258            let result = self.unchecked_set(index, opaque);
259            if result.is_ok() {
260                core::mem::forget(value);
261            }
262            result
263        }
264    }
265
266    /// Sets a field's value by its name in a type-safe manner.
267    ///
268    /// This method takes ownership of the value and ensures proper memory management.
269    ///
270    /// # Errors
271    ///
272    /// Returns an error if:
273    /// - The field name doesn't exist
274    /// - The field shapes don't match
275    pub fn set_by_name<T: crate::Facet>(&mut self, name: &str, value: T) -> Result<(), FieldError> {
276        let index = self
277            .def
278            .fields
279            .iter()
280            .position(|f| f.name == name)
281            .ok_or(FieldError::NoSuchStaticField)?;
282
283        self.set(index, value)
284    }
285
286    /// Marks a field as initialized.
287    ///
288    /// # Safety
289    ///
290    /// The caller must ensure that the field is initialized. Only call this after writing to
291    /// an address gotten through [`Self::field`] or [`Self::field_by_name`].
292    pub unsafe fn mark_initialized(&mut self, index: usize) {
293        self.iset.set(index);
294    }
295
296    /// Gets the struct definition
297    pub fn def(&self) -> StructDef {
298        self.def
299    }
300}
301
302impl Drop for PokeStruct<'_> {
303    fn drop(&mut self) {
304        self.def
305            .fields
306            .iter()
307            .enumerate()
308            .filter_map(|(i, field)| {
309                if self.iset.has(i) {
310                    Some((field, field.shape.vtable.drop_in_place?))
311                } else {
312                    None
313                }
314            })
315            .for_each(|(field, drop_fn)| unsafe {
316                drop_fn(self.data.field_init(field.offset));
317            });
318    }
319}