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