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}