facet_reflect/partial/
mod.rs

1//! Partial value construction for dynamic reflection
2//!
3//! This module provides APIs for incrementally building values through reflection,
4//! particularly useful when deserializing data from external formats like JSON or YAML.
5//!
6//! # Overview
7//!
8//! The `Partial` type (formerly known as `Wip` - Work In Progress) allows you to:
9//! - Allocate memory for a value based on its `Shape`
10//! - Initialize fields incrementally in a type-safe manner
11//! - Handle complex nested structures including structs, enums, collections, and smart pointers
12//! - Build the final value once all required fields are initialized
13//!
14//! # Basic Usage
15//!
16//! ```no_run
17//! # use facet_reflect::Partial;
18//! # use facet_core::{Shape, Facet};
19//! # fn example<T: Facet<'static>>() -> Result<(), Box<dyn std::error::Error>> {
20//! // Allocate memory for a struct
21//! let mut partial = Partial::alloc::<T>()?;
22//!
23//! // Set simple fields
24//! partial.set_field("name", "Alice")?;
25//! partial.set_field("age", 30u32)?;
26//!
27//! // Work with nested structures
28//! partial.begin_field("address")?;
29//! partial.set_field("street", "123 Main St")?;
30//! partial.set_field("city", "Springfield")?;
31//! partial.end()?;
32//!
33//! // Build the final value
34//! let value = partial.build()?;
35//! # Ok(())
36//! # }
37//! ```
38//!
39//! # Chaining Style
40//!
41//! The API supports method chaining for cleaner code:
42//!
43//! ```no_run
44//! # use facet_reflect::Partial;
45//! # use facet_core::{Shape, Facet};
46//! # fn example<T: Facet<'static>>() -> Result<(), Box<dyn std::error::Error>> {
47//! let value = Partial::alloc::<T>()?
48//!     .set_field("name", "Bob")?
49//!     .begin_field("scores")?
50//!         .set(vec![95, 87, 92])?
51//!     .end()?
52//!     .build()?;
53//! # Ok(())
54//! # }
55//! ```
56//!
57//! # Working with Collections
58//!
59//! ```no_run
60//! # use facet_reflect::Partial;
61//! # use facet_core::{Shape, Facet};
62//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
63//! let mut partial = Partial::alloc::<Vec<String>>()?;
64//!
65//! // Add items to a list
66//! partial.begin_list_item()?;
67//! partial.set("first")?;
68//! partial.end()?;
69//!
70//! partial.begin_list_item()?;
71//! partial.set("second")?;
72//! partial.end()?;
73//!
74//! let vec = partial.build()?;
75//! # Ok(())
76//! # }
77//! ```
78//!
79//! # Working with Maps
80//!
81//! ```no_run
82//! # use facet_reflect::Partial;
83//! # use facet_core::{Shape, Facet};
84//! # use std::collections::HashMap;
85//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
86//! let mut partial = Partial::alloc::<HashMap<String, i32>>()?;
87//!
88//! // Insert key-value pairs
89//! partial.begin_key()?;
90//! partial.set("score")?;
91//! partial.end()?;
92//! partial.begin_value()?;
93//! partial.set(100i32)?;
94//! partial.end()?;
95//!
96//! let map = partial.build()?;
97//! # Ok(())
98//! # }
99//! ```
100//!
101//! # Safety and Memory Management
102//!
103//! The `Partial` type ensures memory safety by:
104//! - Tracking initialization state of all fields
105//! - Preventing use-after-build through state tracking
106//! - Properly handling drop semantics for partially initialized values
107//! - Supporting both owned and borrowed values through lifetime parameters
108
109use alloc::vec::Vec;
110
111#[cfg(test)]
112mod tests;
113
114mod iset;
115
116mod partial_api;
117
118mod typed;
119pub use typed::*;
120
121use crate::{ReflectError, TrackerKind, trace};
122
123use core::{marker::PhantomData, ptr::NonNull};
124
125mod heap_value;
126pub use heap_value::*;
127
128use facet_core::{
129    Def, EnumType, PtrMut, PtrUninit, Shape, SliceBuilderVTable, Type, UserType, Variant,
130};
131use iset::ISet;
132
133/// State of a partial value
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135enum PartialState {
136    /// Partial is active and can be modified
137    Active,
138
139    /// Partial has been successfully built and cannot be reused
140    Built,
141
142    /// Building failed and Partial is poisoned
143    BuildFailed,
144}
145
146/// A type-erased, heap-allocated, partially-initialized value.
147///
148/// [Partial] keeps track of the state of initialiation of the underlying
149/// value: if we're building `struct S { a: u32, b: String }`, we may
150/// have initialized `a`, or `b`, or both, or neither.
151///
152/// [Partial] allows navigating down nested structs and initializing them
153/// progressively: [Partial::begin_field] pushes a frame onto the stack,
154/// which then has to be initialized, and popped off with [Partial::end].
155///
156/// If [Partial::end] is called but the current frame isn't fully initialized,
157/// an error is returned: in other words, if you navigate down to a field,
158/// you have to fully initialize it one go. You can't go back up and back down
159/// to it again.
160///
161/// You might be interested in [TypedPartial] as well, which carries a generic
162/// type parameter to make [TypedPartial::build] type-safe. However, when
163/// implementing deserializers for example, if you want to avoid monomorphization,
164/// you might want to work with [Partial] directly.
165pub struct Partial<'facet> {
166    /// stack of frames to keep track of deeply nested initialization
167    frames: Vec<Frame>,
168
169    /// current state of the Partial
170    state: PartialState,
171
172    invariant: PhantomData<fn(&'facet ()) -> &'facet ()>,
173}
174
175#[derive(Clone, Copy, Debug)]
176enum MapInsertState {
177    /// Not currently inserting
178    Idle,
179
180    /// Pushing key
181    PushingKey {
182        /// Temporary storage for the key being built
183        key_ptr: Option<PtrUninit<'static>>,
184    },
185
186    /// Pushing value after key is done
187    PushingValue {
188        /// Temporary storage for the key that was built
189        key_ptr: PtrUninit<'static>,
190        /// Temporary storage for the value being built
191        value_ptr: Option<PtrUninit<'static>>,
192    },
193}
194
195#[derive(Debug)]
196enum FrameOwnership {
197    /// This frame owns the allocation and should deallocate it on drop
198    Owned,
199
200    /// This frame is a field pointer into a parent allocation
201    Field,
202
203    /// This frame's allocation is managed elsewhere (e.g., in MapInsertState)
204    ManagedElsewhere,
205}
206
207/// Points somewhere in a partially-initialized value. If we're initializing
208/// `a.b.c`, then the first frame would point to the beginning of `a`, the
209/// second to the beginning of the `b` field of `a`, etc.
210///
211/// A frame can point to a complex data structure, like a struct or an enum:
212/// it keeps track of whether a variant was selected, which fields are initialized,
213/// etc. and is able to drop & deinitialize
214#[must_use]
215struct Frame {
216    /// Address of the value being initialized
217    data: PtrUninit<'static>,
218
219    /// Shape of the value being initialized
220    shape: &'static Shape,
221
222    /// Tracks initialized fields
223    tracker: Tracker,
224
225    /// Whether this frame owns the allocation or is just a field pointer
226    ownership: FrameOwnership,
227}
228
229#[derive(Debug)]
230enum Tracker {
231    /// Wholly uninitialized
232    Uninit,
233
234    /// Wholly initialized
235    Init,
236
237    /// Partially initialized array
238    Array {
239        /// Track which array elements are initialized (up to 63 elements)
240        iset: ISet,
241
242        /// If we're pushing another frame, this is set to the array index
243        current_child: Option<usize>,
244    },
245
246    /// Partially initialized struct/tuple-struct etc.
247    Struct {
248        /// fields need to be individually tracked — we only
249        /// support up to 63 fields.
250        iset: ISet,
251
252        /// if we're pushing another frame, this is set to the
253        /// index of the struct field
254        current_child: Option<usize>,
255    },
256
257    /// Smart pointer being initialized
258    SmartPointer {
259        /// Whether the inner value has been initialized
260        is_initialized: bool,
261    },
262
263    /// We're initializing an `Arc<[T]>`, `Box<[T]>`, `Rc<[T]>`, etc.
264    ///
265    /// We're using the slice builder API to construct the slice
266    SmartPointerSlice {
267        /// The slice builder vtable
268        vtable: &'static SliceBuilderVTable,
269
270        /// Whether we're currently building an item to push
271        building_item: bool,
272    },
273
274    /// Partially initialized enum (but we picked a variant,
275    /// so it's not Uninit)
276    Enum {
277        /// Variant chosen for the enum
278        variant: &'static Variant,
279
280        /// tracks enum fields (for the given variant)
281        data: ISet,
282
283        /// If we're pushing another frame, this is set to the field index
284        current_child: Option<usize>,
285    },
286
287    /// Partially initialized list (Vec, etc.)
288    List {
289        /// The list has been initialized with capacity
290        is_initialized: bool,
291        /// If we're pushing another frame for an element
292        current_child: bool,
293    },
294
295    /// Partially initialized map (HashMap, BTreeMap, etc.)
296    Map {
297        /// The map has been initialized with capacity
298        is_initialized: bool,
299        /// State of the current insertion operation
300        insert_state: MapInsertState,
301    },
302
303    /// Option being initialized with Some(inner_value)
304    Option {
305        /// Whether we're currently building the inner value
306        building_inner: bool,
307    },
308}
309
310impl Tracker {
311    fn kind(&self) -> TrackerKind {
312        match self {
313            Tracker::Uninit => TrackerKind::Uninit,
314            Tracker::Init => TrackerKind::Init,
315            Tracker::Array { .. } => TrackerKind::Array,
316            Tracker::Struct { .. } => TrackerKind::Struct,
317            Tracker::SmartPointer { .. } => TrackerKind::SmartPointer,
318            Tracker::SmartPointerSlice { .. } => TrackerKind::SmartPointerSlice,
319            Tracker::Enum { .. } => TrackerKind::Enum,
320            Tracker::List { .. } => TrackerKind::List,
321            Tracker::Map { .. } => TrackerKind::Map,
322            Tracker::Option { .. } => TrackerKind::Option,
323        }
324    }
325}
326
327impl Frame {
328    fn new(data: PtrUninit<'static>, shape: &'static Shape, ownership: FrameOwnership) -> Self {
329        // For empty structs (structs with 0 fields), start as Init since there's nothing to initialize
330        // This includes empty tuples () which are zero-sized types with no fields to initialize
331        let tracker = match shape.ty {
332            Type::User(UserType::Struct(struct_type)) if struct_type.fields.is_empty() => {
333                Tracker::Init
334            }
335            _ => Tracker::Uninit,
336        };
337
338        Self {
339            data,
340            shape,
341            tracker,
342            ownership,
343        }
344    }
345
346    /// Deinitialize any initialized field: calls `drop_in_place` but does not free any
347    /// memory even if the frame owns that memory.
348    ///
349    /// After this call, the [Tracker] should be back to [Tracker::Uninit]
350    fn deinit(&mut self) {
351        match &self.tracker {
352            Tracker::Uninit => {
353                // Nothing was initialized, nothing to drop
354            }
355            Tracker::Init => {
356                // Fully initialized, drop it
357                if let Some(drop_fn) = self.shape.vtable.drop_in_place {
358                    unsafe { drop_fn(self.data.assume_init()) };
359                }
360            }
361            Tracker::Array { iset, .. } => {
362                // Drop initialized array elements
363                if let Type::Sequence(facet_core::SequenceType::Array(array_def)) = self.shape.ty {
364                    let element_layout = array_def.t.layout.sized_layout().ok();
365                    if let Some(layout) = element_layout {
366                        for idx in 0..array_def.n {
367                            if iset.get(idx) {
368                                let offset = layout.size() * idx;
369                                let element_ptr = unsafe { self.data.field_init_at(offset) };
370                                if let Some(drop_fn) = array_def.t.vtable.drop_in_place {
371                                    unsafe { drop_fn(element_ptr) };
372                                }
373                            }
374                        }
375                    }
376                }
377            }
378            Tracker::Struct { iset, .. } => {
379                // Drop initialized struct fields
380                if let Type::User(UserType::Struct(struct_type)) = self.shape.ty {
381                    for (idx, field) in struct_type.fields.iter().enumerate() {
382                        if iset.get(idx) {
383                            // This field was initialized, drop it
384                            let field_ptr = unsafe { self.data.field_init_at(field.offset) };
385                            if let Some(drop_fn) = field.shape().vtable.drop_in_place {
386                                unsafe { drop_fn(field_ptr) };
387                            }
388                        }
389                    }
390                }
391            }
392            Tracker::Enum { variant, data, .. } => {
393                // Drop initialized enum variant fields
394                for (idx, field) in variant.data.fields.iter().enumerate() {
395                    if data.get(idx) {
396                        // This field was initialized, drop it
397                        let field_ptr = unsafe { self.data.as_mut_byte_ptr().add(field.offset) };
398                        if let Some(drop_fn) = field.shape().vtable.drop_in_place {
399                            unsafe { drop_fn(PtrMut::new(NonNull::new_unchecked(field_ptr))) };
400                        }
401                    }
402                }
403            }
404            Tracker::SmartPointer { is_initialized } => {
405                // Drop the initialized Box
406                if *is_initialized {
407                    if let Some(drop_fn) = self.shape.vtable.drop_in_place {
408                        unsafe { drop_fn(self.data.assume_init()) };
409                    }
410                }
411                // Note: we don't deallocate the inner value here because
412                // the Box's drop will handle that
413            }
414            Tracker::SmartPointerSlice { vtable, .. } => {
415                // Free the slice builder
416                let builder_ptr = unsafe { self.data.assume_init() };
417                unsafe {
418                    (vtable.free_fn)(builder_ptr);
419                }
420            }
421            Tracker::List { is_initialized, .. } => {
422                // Drop the initialized List
423                if *is_initialized {
424                    if let Some(drop_fn) = self.shape.vtable.drop_in_place {
425                        unsafe { drop_fn(self.data.assume_init()) };
426                    }
427                }
428            }
429            Tracker::Map {
430                is_initialized,
431                insert_state,
432            } => {
433                // Drop the initialized Map
434                if *is_initialized {
435                    if let Some(drop_fn) = self.shape.vtable.drop_in_place {
436                        unsafe { drop_fn(self.data.assume_init()) };
437                    }
438                }
439
440                // Clean up any in-progress insertion state
441                match insert_state {
442                    MapInsertState::PushingKey { key_ptr } => {
443                        if let Some(key_ptr) = key_ptr {
444                            // Deallocate the key buffer
445                            if let Def::Map(map_def) = self.shape.def {
446                                if let Ok(key_shape) = map_def.k().layout.sized_layout() {
447                                    if key_shape.size() > 0 {
448                                        unsafe {
449                                            alloc::alloc::dealloc(
450                                                key_ptr.as_mut_byte_ptr(),
451                                                key_shape,
452                                            )
453                                        };
454                                    }
455                                }
456                            }
457                        }
458                    }
459                    MapInsertState::PushingValue { key_ptr, value_ptr } => {
460                        // Drop and deallocate both key and value buffers
461                        if let Def::Map(map_def) = self.shape.def {
462                            // Drop and deallocate the key
463                            if let Some(drop_fn) = map_def.k().vtable.drop_in_place {
464                                unsafe { drop_fn(key_ptr.assume_init()) };
465                            }
466                            if let Ok(key_shape) = map_def.k().layout.sized_layout() {
467                                if key_shape.size() > 0 {
468                                    unsafe {
469                                        alloc::alloc::dealloc(key_ptr.as_mut_byte_ptr(), key_shape)
470                                    };
471                                }
472                            }
473
474                            // Drop and deallocate the value if it exists
475                            if let Some(value_ptr) = value_ptr {
476                                if let Ok(value_shape) = map_def.v().layout.sized_layout() {
477                                    if value_shape.size() > 0 {
478                                        unsafe {
479                                            alloc::alloc::dealloc(
480                                                value_ptr.as_mut_byte_ptr(),
481                                                value_shape,
482                                            )
483                                        };
484                                    }
485                                }
486                            }
487                        }
488                    }
489                    MapInsertState::Idle => {}
490                }
491            }
492            Tracker::Option { building_inner } => {
493                // If we're building the inner value, it will be handled by the Option vtable
494                // No special cleanup needed here as the Option will either be properly
495                // initialized or remain uninitialized
496                if !building_inner {
497                    // Option is fully initialized, drop it normally
498                    if let Some(drop_fn) = self.shape.vtable.drop_in_place {
499                        unsafe { drop_fn(self.data.assume_init()) };
500                    }
501                }
502            }
503        }
504
505        self.tracker = Tracker::Uninit;
506    }
507
508    /// This must be called after (fully) initializing a value.
509    ///
510    /// This will most often result in a transition to [Tracker::Init] although
511    /// composite types (structs, enums, etc.) might be handled differently
512    ///
513    /// # Safety
514    ///
515    /// This should only be called when `self.data` has been actually initialized.
516    unsafe fn mark_as_init(&mut self) {
517        self.tracker = Tracker::Init;
518    }
519
520    /// Deallocate the memory associated with this frame, if it owns it.
521    ///
522    /// The memory has to be deinitialized first, see [Frame::deinit]
523    fn dealloc(self) {
524        if !matches!(self.tracker, Tracker::Uninit) {
525            unreachable!("a frame has to be deinitialized before being deallocated")
526        }
527
528        // Now, deallocate temporary String allocation if necessary
529        if let FrameOwnership::Owned = self.ownership {
530            if let Ok(layout) = self.shape.layout.sized_layout() {
531                if layout.size() > 0 {
532                    unsafe { alloc::alloc::dealloc(self.data.as_mut_byte_ptr(), layout) };
533                }
534            }
535            // no need to update `self.ownership` since `self` drops at the end of this
536        }
537    }
538
539    /// Returns an error if the value is not fully initialized
540    fn require_full_initialization(&self) -> Result<(), ReflectError> {
541        match self.tracker {
542            Tracker::Uninit => Err(ReflectError::UninitializedValue { shape: self.shape }),
543            Tracker::Init => Ok(()),
544            Tracker::Array { iset, .. } => {
545                match self.shape.ty {
546                    Type::Sequence(facet_core::SequenceType::Array(array_def)) => {
547                        // Check if all array elements are initialized
548                        if (0..array_def.n).all(|idx| iset.get(idx)) {
549                            Ok(())
550                        } else {
551                            Err(ReflectError::UninitializedValue { shape: self.shape })
552                        }
553                    }
554                    _ => Err(ReflectError::UninitializedValue { shape: self.shape }),
555                }
556            }
557            Tracker::Struct { iset, .. } => {
558                if iset.all_set() {
559                    Ok(())
560                } else {
561                    // Attempt to find the first uninitialized field, if possible
562                    match self.shape.ty {
563                        Type::User(UserType::Struct(struct_type)) => {
564                            // Find index of the first bit not set
565                            let first_missing_idx =
566                                (0..struct_type.fields.len()).find(|&idx| !iset.get(idx));
567                            if let Some(missing_idx) = first_missing_idx {
568                                let field_name = struct_type.fields[missing_idx].name;
569                                Err(ReflectError::UninitializedField {
570                                    shape: self.shape,
571                                    field_name,
572                                })
573                            } else {
574                                // fallback, something went wrong
575                                Err(ReflectError::UninitializedValue { shape: self.shape })
576                            }
577                        }
578                        _ => Err(ReflectError::UninitializedValue { shape: self.shape }),
579                    }
580                }
581            }
582            Tracker::Enum { variant, data, .. } => {
583                // Check if all fields of the variant are initialized
584                let num_fields = variant.data.fields.len();
585                if num_fields == 0 {
586                    // Unit variant, always initialized
587                    Ok(())
588                } else if (0..num_fields).all(|idx| data.get(idx)) {
589                    Ok(())
590                } else {
591                    // Find the first uninitialized field
592                    let first_missing_idx = (0..num_fields).find(|&idx| !data.get(idx));
593                    if let Some(missing_idx) = first_missing_idx {
594                        let field_name = variant.data.fields[missing_idx].name;
595                        Err(ReflectError::UninitializedEnumField {
596                            shape: self.shape,
597                            field_name,
598                            variant_name: variant.name,
599                        })
600                    } else {
601                        Err(ReflectError::UninitializedValue { shape: self.shape })
602                    }
603                }
604            }
605            Tracker::SmartPointer { is_initialized } => {
606                if is_initialized {
607                    Ok(())
608                } else {
609                    Err(ReflectError::UninitializedValue { shape: self.shape })
610                }
611            }
612            Tracker::SmartPointerSlice { building_item, .. } => {
613                if building_item {
614                    Err(ReflectError::UninitializedValue { shape: self.shape })
615                } else {
616                    Ok(())
617                }
618            }
619            Tracker::List { is_initialized, .. } => {
620                if is_initialized {
621                    Ok(())
622                } else {
623                    Err(ReflectError::UninitializedValue { shape: self.shape })
624                }
625            }
626            Tracker::Map {
627                is_initialized,
628                insert_state,
629            } => {
630                if is_initialized && matches!(insert_state, MapInsertState::Idle) {
631                    Ok(())
632                } else {
633                    Err(ReflectError::UninitializedValue { shape: self.shape })
634                }
635            }
636            Tracker::Option { building_inner } => {
637                if building_inner {
638                    Err(ReflectError::UninitializedValue { shape: self.shape })
639                } else {
640                    Ok(())
641                }
642            }
643        }
644    }
645
646    /// Get the [EnumType] of the frame's shape, if it is an enum type
647    pub(crate) fn get_enum_type(&self) -> Result<EnumType, ReflectError> {
648        match self.shape.ty {
649            Type::User(UserType::Enum(e)) => Ok(e),
650            _ => Err(ReflectError::WasNotA {
651                expected: "enum",
652                actual: self.shape,
653            }),
654        }
655    }
656}
657
658impl<'facet> Drop for Partial<'facet> {
659    fn drop(&mut self) {
660        trace!("🧹 Partial is being dropped");
661
662        // We need to properly drop all initialized fields
663        while let Some(mut frame) = self.frames.pop() {
664            frame.deinit();
665
666            // Only deallocate if this frame owns the allocation
667            if let FrameOwnership::Owned = frame.ownership {
668                if let Ok(layout) = frame.shape.layout.sized_layout() {
669                    if layout.size() > 0 {
670                        unsafe { alloc::alloc::dealloc(frame.data.as_mut_byte_ptr(), layout) };
671                    }
672                }
673            }
674        }
675    }
676}