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}