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, Field, 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    /// Whether this frame is for a custom deserialization pipeline
229    using_custom_deserialization: bool,
230}
231
232#[derive(Debug)]
233enum Tracker {
234    /// Wholly uninitialized
235    Uninit,
236
237    /// Wholly initialized
238    Init,
239
240    /// Partially initialized array
241    Array {
242        /// Track which array elements are initialized (up to 63 elements)
243        iset: ISet,
244
245        /// If we're pushing another frame, this is set to the array index
246        current_child: Option<usize>,
247    },
248
249    /// Partially initialized struct/tuple-struct etc.
250    Struct {
251        /// fields need to be individually tracked — we only
252        /// support up to 63 fields.
253        iset: ISet,
254
255        /// if we're pushing another frame, this is set to the
256        /// index of the struct field
257        current_child: Option<usize>,
258    },
259
260    /// Smart pointer being initialized
261    SmartPointer {
262        /// Whether the inner value has been initialized
263        is_initialized: bool,
264    },
265
266    /// We're initializing an `Arc<[T]>`, `Box<[T]>`, `Rc<[T]>`, etc.
267    ///
268    /// We're using the slice builder API to construct the slice
269    SmartPointerSlice {
270        /// The slice builder vtable
271        vtable: &'static SliceBuilderVTable,
272
273        /// Whether we're currently building an item to push
274        building_item: bool,
275    },
276
277    /// Partially initialized enum (but we picked a variant,
278    /// so it's not Uninit)
279    Enum {
280        /// Variant chosen for the enum
281        variant: &'static Variant,
282
283        /// tracks enum fields (for the given variant)
284        data: ISet,
285
286        /// If we're pushing another frame, this is set to the field index
287        current_child: Option<usize>,
288    },
289
290    /// Partially initialized list (Vec, etc.)
291    List {
292        /// The list has been initialized with capacity
293        is_initialized: bool,
294        /// If we're pushing another frame for an element
295        current_child: bool,
296    },
297
298    /// Partially initialized map (HashMap, BTreeMap, etc.)
299    Map {
300        /// The map has been initialized with capacity
301        is_initialized: bool,
302        /// State of the current insertion operation
303        insert_state: MapInsertState,
304    },
305
306    /// Partially initialized set (HashSet, BTreeSet, etc.)
307    Set {
308        /// The set has been initialized with capacity
309        is_initialized: bool,
310        /// If we're pushing another frame for an element
311        current_child: bool,
312    },
313
314    /// Option being initialized with Some(inner_value)
315    Option {
316        /// Whether we're currently building the inner value
317        building_inner: bool,
318    },
319}
320
321impl Tracker {
322    fn kind(&self) -> TrackerKind {
323        match self {
324            Tracker::Uninit => TrackerKind::Uninit,
325            Tracker::Init => TrackerKind::Init,
326            Tracker::Array { .. } => TrackerKind::Array,
327            Tracker::Struct { .. } => TrackerKind::Struct,
328            Tracker::SmartPointer { .. } => TrackerKind::SmartPointer,
329            Tracker::SmartPointerSlice { .. } => TrackerKind::SmartPointerSlice,
330            Tracker::Enum { .. } => TrackerKind::Enum,
331            Tracker::List { .. } => TrackerKind::List,
332            Tracker::Map { .. } => TrackerKind::Map,
333            Tracker::Set { .. } => TrackerKind::Set,
334            Tracker::Option { .. } => TrackerKind::Option,
335        }
336    }
337}
338
339impl Frame {
340    fn new(data: PtrUninit<'static>, shape: &'static Shape, ownership: FrameOwnership) -> Self {
341        // For empty structs (structs with 0 fields), start as Init since there's nothing to initialize
342        // This includes empty tuples () which are zero-sized types with no fields to initialize
343        let tracker = match shape.ty {
344            Type::User(UserType::Struct(struct_type)) if struct_type.fields.is_empty() => {
345                Tracker::Init
346            }
347            _ => Tracker::Uninit,
348        };
349
350        Self {
351            data,
352            shape,
353            tracker,
354            ownership,
355            using_custom_deserialization: false,
356        }
357    }
358
359    /// Deinitialize any initialized field: calls `drop_in_place` but does not free any
360    /// memory even if the frame owns that memory.
361    ///
362    /// After this call, the [Tracker] should be back to [Tracker::Uninit]
363    fn deinit(&mut self) {
364        match &self.tracker {
365            Tracker::Uninit => {
366                // Nothing was initialized, nothing to drop
367            }
368            Tracker::Init => {
369                // Fully initialized, drop it
370                if let Some(drop_fn) = self.shape.vtable.drop_in_place {
371                    unsafe { drop_fn(self.data.assume_init()) };
372                }
373            }
374            Tracker::Array { iset, .. } => {
375                // Drop initialized array elements
376                if let Type::Sequence(facet_core::SequenceType::Array(array_def)) = self.shape.ty {
377                    let element_layout = array_def.t.layout.sized_layout().ok();
378                    if let Some(layout) = element_layout {
379                        for idx in 0..array_def.n {
380                            if iset.get(idx) {
381                                let offset = layout.size() * idx;
382                                let element_ptr = unsafe { self.data.field_init_at(offset) };
383                                if let Some(drop_fn) = array_def.t.vtable.drop_in_place {
384                                    unsafe { drop_fn(element_ptr) };
385                                }
386                            }
387                        }
388                    }
389                }
390            }
391            Tracker::Struct { iset, .. } => {
392                // Drop initialized struct fields
393                if let Type::User(UserType::Struct(struct_type)) = self.shape.ty {
394                    if iset.all_set() && self.shape.vtable.drop_in_place.is_some() {
395                        unsafe {
396                            (self.shape.vtable.drop_in_place.unwrap())(self.data.assume_init())
397                        };
398                    } else {
399                        for (idx, field) in struct_type.fields.iter().enumerate() {
400                            if iset.get(idx) {
401                                // This field was initialized, drop it
402                                let field_ptr = unsafe { self.data.field_init_at(field.offset) };
403                                if let Some(drop_fn) = field.shape().vtable.drop_in_place {
404                                    unsafe { drop_fn(field_ptr) };
405                                }
406                            }
407                        }
408                    }
409                }
410            }
411            Tracker::Enum { variant, data, .. } => {
412                // Drop initialized enum variant fields
413                for (idx, field) in variant.data.fields.iter().enumerate() {
414                    if data.get(idx) {
415                        // This field was initialized, drop it
416                        let field_ptr = unsafe { self.data.as_mut_byte_ptr().add(field.offset) };
417                        if let Some(drop_fn) = field.shape().vtable.drop_in_place {
418                            unsafe { drop_fn(PtrMut::new(NonNull::new_unchecked(field_ptr))) };
419                        }
420                    }
421                }
422            }
423            Tracker::SmartPointer { is_initialized } => {
424                // Drop the initialized Box
425                if *is_initialized {
426                    if let Some(drop_fn) = self.shape.vtable.drop_in_place {
427                        unsafe { drop_fn(self.data.assume_init()) };
428                    }
429                }
430                // Note: we don't deallocate the inner value here because
431                // the Box's drop will handle that
432            }
433            Tracker::SmartPointerSlice { vtable, .. } => {
434                // Free the slice builder
435                let builder_ptr = unsafe { self.data.assume_init() };
436                unsafe {
437                    (vtable.free_fn)(builder_ptr);
438                }
439            }
440            Tracker::List { is_initialized, .. } => {
441                // Drop the initialized List
442                if *is_initialized {
443                    if let Some(drop_fn) = self.shape.vtable.drop_in_place {
444                        unsafe { drop_fn(self.data.assume_init()) };
445                    }
446                }
447            }
448            Tracker::Map {
449                is_initialized,
450                insert_state,
451            } => {
452                // Drop the initialized Map
453                if *is_initialized {
454                    if let Some(drop_fn) = self.shape.vtable.drop_in_place {
455                        unsafe { drop_fn(self.data.assume_init()) };
456                    }
457                }
458
459                // Clean up any in-progress insertion state
460                match insert_state {
461                    MapInsertState::PushingKey { key_ptr } => {
462                        if let Some(key_ptr) = key_ptr {
463                            // Deallocate the key buffer
464                            if let Def::Map(map_def) = self.shape.def {
465                                if let Ok(key_shape) = map_def.k().layout.sized_layout() {
466                                    if key_shape.size() > 0 {
467                                        unsafe {
468                                            alloc::alloc::dealloc(
469                                                key_ptr.as_mut_byte_ptr(),
470                                                key_shape,
471                                            )
472                                        };
473                                    }
474                                }
475                            }
476                        }
477                    }
478                    MapInsertState::PushingValue { key_ptr, value_ptr } => {
479                        // Drop and deallocate both key and value buffers
480                        if let Def::Map(map_def) = self.shape.def {
481                            // Drop and deallocate the key
482                            if let Some(drop_fn) = map_def.k().vtable.drop_in_place {
483                                unsafe { drop_fn(key_ptr.assume_init()) };
484                            }
485                            if let Ok(key_shape) = map_def.k().layout.sized_layout() {
486                                if key_shape.size() > 0 {
487                                    unsafe {
488                                        alloc::alloc::dealloc(key_ptr.as_mut_byte_ptr(), key_shape)
489                                    };
490                                }
491                            }
492
493                            // Drop and deallocate the value if it exists
494                            if let Some(value_ptr) = value_ptr {
495                                if let Ok(value_shape) = map_def.v().layout.sized_layout() {
496                                    if value_shape.size() > 0 {
497                                        unsafe {
498                                            alloc::alloc::dealloc(
499                                                value_ptr.as_mut_byte_ptr(),
500                                                value_shape,
501                                            )
502                                        };
503                                    }
504                                }
505                            }
506                        }
507                    }
508                    MapInsertState::Idle => {}
509                }
510            }
511            Tracker::Set { is_initialized, .. } => {
512                // Drop the initialized Set
513                if *is_initialized {
514                    if let Some(drop_fn) = self.shape.vtable.drop_in_place {
515                        unsafe { drop_fn(self.data.assume_init()) };
516                    }
517                }
518            }
519            Tracker::Option { building_inner } => {
520                // If we're building the inner value, it will be handled by the Option vtable
521                // No special cleanup needed here as the Option will either be properly
522                // initialized or remain uninitialized
523                if !building_inner {
524                    // Option is fully initialized, drop it normally
525                    if let Some(drop_fn) = self.shape.vtable.drop_in_place {
526                        unsafe { drop_fn(self.data.assume_init()) };
527                    }
528                }
529            }
530        }
531
532        self.tracker = Tracker::Uninit;
533    }
534
535    /// This must be called after (fully) initializing a value.
536    ///
537    /// This will most often result in a transition to [Tracker::Init] although
538    /// composite types (structs, enums, etc.) might be handled differently
539    ///
540    /// # Safety
541    ///
542    /// This should only be called when `self.data` has been actually initialized.
543    unsafe fn mark_as_init(&mut self) {
544        self.tracker = Tracker::Init;
545    }
546
547    /// Deallocate the memory associated with this frame, if it owns it.
548    ///
549    /// The memory has to be deinitialized first, see [Frame::deinit]
550    fn dealloc(self) {
551        if !matches!(self.tracker, Tracker::Uninit) {
552            unreachable!("a frame has to be deinitialized before being deallocated")
553        }
554
555        // Now, deallocate temporary String allocation if necessary
556        if let FrameOwnership::Owned = self.ownership {
557            if let Ok(layout) = self.shape.layout.sized_layout() {
558                if layout.size() > 0 {
559                    unsafe { alloc::alloc::dealloc(self.data.as_mut_byte_ptr(), layout) };
560                }
561            }
562            // no need to update `self.ownership` since `self` drops at the end of this
563        }
564    }
565
566    /// Returns an error if the value is not fully initialized
567    fn require_full_initialization(&self) -> Result<(), ReflectError> {
568        match self.tracker {
569            Tracker::Uninit => Err(ReflectError::UninitializedValue { shape: self.shape }),
570            Tracker::Init => Ok(()),
571            Tracker::Array { iset, .. } => {
572                match self.shape.ty {
573                    Type::Sequence(facet_core::SequenceType::Array(array_def)) => {
574                        // Check if all array elements are initialized
575                        if (0..array_def.n).all(|idx| iset.get(idx)) {
576                            Ok(())
577                        } else {
578                            Err(ReflectError::UninitializedValue { shape: self.shape })
579                        }
580                    }
581                    _ => Err(ReflectError::UninitializedValue { shape: self.shape }),
582                }
583            }
584            Tracker::Struct { iset, .. } => {
585                if iset.all_set() {
586                    Ok(())
587                } else {
588                    // Attempt to find the first uninitialized field, if possible
589                    match self.shape.ty {
590                        Type::User(UserType::Struct(struct_type)) => {
591                            // Find index of the first bit not set
592                            let first_missing_idx =
593                                (0..struct_type.fields.len()).find(|&idx| !iset.get(idx));
594                            if let Some(missing_idx) = first_missing_idx {
595                                let field_name = struct_type.fields[missing_idx].name;
596                                Err(ReflectError::UninitializedField {
597                                    shape: self.shape,
598                                    field_name,
599                                })
600                            } else {
601                                // fallback, something went wrong
602                                Err(ReflectError::UninitializedValue { shape: self.shape })
603                            }
604                        }
605                        _ => Err(ReflectError::UninitializedValue { shape: self.shape }),
606                    }
607                }
608            }
609            Tracker::Enum { variant, data, .. } => {
610                // Check if all fields of the variant are initialized
611                let num_fields = variant.data.fields.len();
612                if num_fields == 0 {
613                    // Unit variant, always initialized
614                    Ok(())
615                } else if (0..num_fields).all(|idx| data.get(idx)) {
616                    Ok(())
617                } else {
618                    // Find the first uninitialized field
619                    let first_missing_idx = (0..num_fields).find(|&idx| !data.get(idx));
620                    if let Some(missing_idx) = first_missing_idx {
621                        let field_name = variant.data.fields[missing_idx].name;
622                        Err(ReflectError::UninitializedEnumField {
623                            shape: self.shape,
624                            field_name,
625                            variant_name: variant.name,
626                        })
627                    } else {
628                        Err(ReflectError::UninitializedValue { shape: self.shape })
629                    }
630                }
631            }
632            Tracker::SmartPointer { is_initialized } => {
633                if is_initialized {
634                    Ok(())
635                } else {
636                    Err(ReflectError::UninitializedValue { shape: self.shape })
637                }
638            }
639            Tracker::SmartPointerSlice { building_item, .. } => {
640                if building_item {
641                    Err(ReflectError::UninitializedValue { shape: self.shape })
642                } else {
643                    Ok(())
644                }
645            }
646            Tracker::List { is_initialized, .. } => {
647                if is_initialized {
648                    Ok(())
649                } else {
650                    Err(ReflectError::UninitializedValue { shape: self.shape })
651                }
652            }
653            Tracker::Map {
654                is_initialized,
655                insert_state,
656            } => {
657                if is_initialized && matches!(insert_state, MapInsertState::Idle) {
658                    Ok(())
659                } else {
660                    Err(ReflectError::UninitializedValue { shape: self.shape })
661                }
662            }
663            Tracker::Set {
664                is_initialized,
665                current_child,
666            } => {
667                if is_initialized && !current_child {
668                    Ok(())
669                } else {
670                    Err(ReflectError::UninitializedValue { shape: self.shape })
671                }
672            }
673            Tracker::Option { building_inner } => {
674                if building_inner {
675                    Err(ReflectError::UninitializedValue { shape: self.shape })
676                } else {
677                    Ok(())
678                }
679            }
680        }
681    }
682
683    /// Get the [EnumType] of the frame's shape, if it is an enum type
684    pub(crate) fn get_enum_type(&self) -> Result<EnumType, ReflectError> {
685        match self.shape.ty {
686            Type::User(UserType::Enum(e)) => Ok(e),
687            _ => Err(ReflectError::WasNotA {
688                expected: "enum",
689                actual: self.shape,
690            }),
691        }
692    }
693
694    pub(crate) fn get_field(&self) -> Option<&Field> {
695        match self.shape.ty {
696            Type::User(user_type) => match user_type {
697                UserType::Struct(struct_type) => {
698                    // Try to get currently active field index
699                    if let Tracker::Struct {
700                        current_child: Some(idx),
701                        ..
702                    } = &self.tracker
703                    {
704                        struct_type.fields.get(*idx)
705                    } else {
706                        None
707                    }
708                }
709                UserType::Enum(_enum_type) => {
710                    if let Tracker::Enum {
711                        variant,
712                        current_child: Some(idx),
713                        ..
714                    } = &self.tracker
715                    {
716                        variant.data.fields.get(*idx)
717                    } else {
718                        None
719                    }
720                }
721                _ => None,
722            },
723            _ => None,
724        }
725    }
726}
727
728impl<'facet> Drop for Partial<'facet> {
729    fn drop(&mut self) {
730        trace!("🧹 Partial is being dropped");
731
732        // We need to properly drop all initialized fields
733        while let Some(mut frame) = self.frames.pop() {
734            frame.deinit();
735
736            // Only deallocate if this frame owns the allocation
737            if let FrameOwnership::Owned = frame.ownership {
738                if let Ok(layout) = frame.shape.layout.sized_layout() {
739                    if layout.size() > 0 {
740                        unsafe { alloc::alloc::dealloc(frame.data.as_mut_byte_ptr(), layout) };
741                    }
742                }
743            }
744        }
745    }
746}