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;
124
125mod heap_value;
126pub use heap_value::*;
127
128use facet_core::{
129 Def, EnumType, PtrMut, PtrUninit, Shape, SliceBuilderVTable, Type, UserType, Variant,
130};
131use iset::ISet;
132
133/// State of a partial value
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135enum PartialState {
136 /// Partial is active and can be modified
137 Active,
138
139 /// Partial has been successfully built and cannot be reused
140 Built,
141
142 /// Building failed and Partial is poisoned
143 BuildFailed,
144}
145
146/// A type-erased, heap-allocated, partially-initialized value.
147///
148/// [Partial] keeps track of the state of initialiation of the underlying
149/// value: if we're building `struct S { a: u32, b: String }`, we may
150/// have initialized `a`, or `b`, or both, or neither.
151///
152/// [Partial] allows navigating down nested structs and initializing them
153/// progressively: [Partial::begin_field] pushes a frame onto the stack,
154/// which then has to be initialized, and popped off with [Partial::end].
155///
156/// If [Partial::end] is called but the current frame isn't fully initialized,
157/// an error is returned: in other words, if you navigate down to a field,
158/// you have to fully initialize it one go. You can't go back up and back down
159/// to it again.
160///
161/// You might be interested in [TypedPartial] as well, which carries a generic
162/// type parameter to make [TypedPartial::build] type-safe. However, when
163/// implementing deserializers for example, if you want to avoid monomorphization,
164/// you might want to work with [Partial] directly.
165pub struct Partial<'facet> {
166 /// stack of frames to keep track of deeply nested initialization
167 frames: Vec<Frame>,
168
169 /// current state of the Partial
170 state: PartialState,
171
172 invariant: PhantomData<fn(&'facet ()) -> &'facet ()>,
173}
174
175#[derive(Clone, Copy, Debug)]
176enum MapInsertState {
177 /// Not currently inserting
178 Idle,
179
180 /// Pushing key
181 PushingKey {
182 /// Temporary storage for the key being built
183 key_ptr: Option<PtrUninit<'static>>,
184 },
185
186 /// Pushing value after key is done
187 PushingValue {
188 /// Temporary storage for the key that was built
189 key_ptr: PtrUninit<'static>,
190 /// Temporary storage for the value being built
191 value_ptr: Option<PtrUninit<'static>>,
192 },
193}
194
195#[derive(Debug)]
196enum FrameOwnership {
197 /// This frame owns the allocation and should deallocate it on drop
198 Owned,
199
200 /// This frame is a field pointer into a parent allocation
201 Field,
202
203 /// This frame's allocation is managed elsewhere (e.g., in MapInsertState)
204 ManagedElsewhere,
205}
206
207/// Points somewhere in a partially-initialized value. If we're initializing
208/// `a.b.c`, then the first frame would point to the beginning of `a`, the
209/// second to the beginning of the `b` field of `a`, etc.
210///
211/// A frame can point to a complex data structure, like a struct or an enum:
212/// it keeps track of whether a variant was selected, which fields are initialized,
213/// etc. and is able to drop & deinitialize
214#[must_use]
215struct Frame {
216 /// Address of the value being initialized
217 data: PtrUninit<'static>,
218
219 /// Shape of the value being initialized
220 shape: &'static Shape,
221
222 /// Tracks initialized fields
223 tracker: Tracker,
224
225 /// Whether this frame owns the allocation or is just a field pointer
226 ownership: FrameOwnership,
227}
228
229#[derive(Debug)]
230enum Tracker {
231 /// Wholly uninitialized
232 Uninit,
233
234 /// Wholly initialized
235 Init,
236
237 /// Partially initialized array
238 Array {
239 /// Track which array elements are initialized (up to 63 elements)
240 iset: ISet,
241
242 /// If we're pushing another frame, this is set to the array index
243 current_child: Option<usize>,
244 },
245
246 /// Partially initialized struct/tuple-struct etc.
247 Struct {
248 /// fields need to be individually tracked — we only
249 /// support up to 63 fields.
250 iset: ISet,
251
252 /// if we're pushing another frame, this is set to the
253 /// index of the struct field
254 current_child: Option<usize>,
255 },
256
257 /// Smart pointer being initialized
258 SmartPointer {
259 /// Whether the inner value has been initialized
260 is_initialized: bool,
261 },
262
263 /// We're initializing an `Arc<[T]>`, `Box<[T]>`, `Rc<[T]>`, etc.
264 ///
265 /// We're using the slice builder API to construct the slice
266 SmartPointerSlice {
267 /// The slice builder vtable
268 vtable: &'static SliceBuilderVTable,
269
270 /// Whether we're currently building an item to push
271 building_item: bool,
272 },
273
274 /// Partially initialized enum (but we picked a variant,
275 /// so it's not Uninit)
276 Enum {
277 /// Variant chosen for the enum
278 variant: &'static Variant,
279
280 /// tracks enum fields (for the given variant)
281 data: ISet,
282
283 /// If we're pushing another frame, this is set to the field index
284 current_child: Option<usize>,
285 },
286
287 /// Partially initialized list (Vec, etc.)
288 List {
289 /// The list has been initialized with capacity
290 is_initialized: bool,
291 /// If we're pushing another frame for an element
292 current_child: bool,
293 },
294
295 /// Partially initialized map (HashMap, BTreeMap, etc.)
296 Map {
297 /// The map has been initialized with capacity
298 is_initialized: bool,
299 /// State of the current insertion operation
300 insert_state: MapInsertState,
301 },
302
303 /// Option being initialized with Some(inner_value)
304 Option {
305 /// Whether we're currently building the inner value
306 building_inner: bool,
307 },
308}
309
310impl Tracker {
311 fn kind(&self) -> TrackerKind {
312 match self {
313 Tracker::Uninit => TrackerKind::Uninit,
314 Tracker::Init => TrackerKind::Init,
315 Tracker::Array { .. } => TrackerKind::Array,
316 Tracker::Struct { .. } => TrackerKind::Struct,
317 Tracker::SmartPointer { .. } => TrackerKind::SmartPointer,
318 Tracker::SmartPointerSlice { .. } => TrackerKind::SmartPointerSlice,
319 Tracker::Enum { .. } => TrackerKind::Enum,
320 Tracker::List { .. } => TrackerKind::List,
321 Tracker::Map { .. } => TrackerKind::Map,
322 Tracker::Option { .. } => TrackerKind::Option,
323 }
324 }
325}
326
327impl Frame {
328 fn new(data: PtrUninit<'static>, shape: &'static Shape, ownership: FrameOwnership) -> Self {
329 // For empty structs (structs with 0 fields), start as Init since there's nothing to initialize
330 // This includes empty tuples () which are zero-sized types with no fields to initialize
331 let tracker = match shape.ty {
332 Type::User(UserType::Struct(struct_type)) if struct_type.fields.is_empty() => {
333 Tracker::Init
334 }
335 _ => Tracker::Uninit,
336 };
337
338 Self {
339 data,
340 shape,
341 tracker,
342 ownership,
343 }
344 }
345
346 /// Deinitialize any initialized field: calls `drop_in_place` but does not free any
347 /// memory even if the frame owns that memory.
348 ///
349 /// After this call, the [Tracker] should be back to [Tracker::Uninit]
350 fn deinit(&mut self) {
351 match &self.tracker {
352 Tracker::Uninit => {
353 // Nothing was initialized, nothing to drop
354 }
355 Tracker::Init => {
356 // Fully initialized, drop it
357 if let Some(drop_fn) = self.shape.vtable.sized().and_then(|v| (v.drop_in_place)()) {
358 unsafe { drop_fn(PtrMut::new(self.data.as_mut_byte_ptr())) };
359 }
360 }
361 Tracker::Array { iset, .. } => {
362 // Drop initialized array elements
363 if let Type::Sequence(facet_core::SequenceType::Array(array_def)) = self.shape.ty {
364 let element_layout = array_def.t.layout.sized_layout().ok();
365 if let Some(layout) = element_layout {
366 for idx in 0..array_def.n {
367 if iset.get(idx) {
368 let offset = layout.size() * idx;
369 let element_ptr = unsafe { self.data.field_init_at(offset) };
370 if let Some(drop_fn) =
371 array_def.t.vtable.sized().and_then(|v| (v.drop_in_place)())
372 {
373 unsafe { drop_fn(element_ptr) };
374 }
375 }
376 }
377 }
378 }
379 }
380 Tracker::Struct { iset, .. } => {
381 // Drop initialized struct fields
382 if let Type::User(UserType::Struct(struct_type)) = self.shape.ty {
383 for (idx, field) in struct_type.fields.iter().enumerate() {
384 if iset.get(idx) {
385 // This field was initialized, drop it
386 let field_ptr = unsafe { self.data.field_init_at(field.offset) };
387 if let Some(drop_fn) =
388 field.shape.vtable.sized().and_then(|v| (v.drop_in_place)())
389 {
390 unsafe { drop_fn(field_ptr) };
391 }
392 }
393 }
394 }
395 }
396 Tracker::Enum { variant, data, .. } => {
397 // Drop initialized enum variant fields
398 for (idx, field) in variant.data.fields.iter().enumerate() {
399 if data.get(idx) {
400 // This field was initialized, drop it
401 let field_ptr = unsafe { self.data.as_mut_byte_ptr().add(field.offset) };
402 if let Some(drop_fn) =
403 field.shape.vtable.sized().and_then(|v| (v.drop_in_place)())
404 {
405 unsafe { drop_fn(PtrMut::new(field_ptr)) };
406 }
407 }
408 }
409 }
410 Tracker::SmartPointer { is_initialized } => {
411 // Drop the initialized Box
412 if *is_initialized {
413 if let Some(drop_fn) =
414 self.shape.vtable.sized().and_then(|v| (v.drop_in_place)())
415 {
416 unsafe { drop_fn(self.data.assume_init()) };
417 }
418 }
419 // Note: we don't deallocate the inner value here because
420 // the Box's drop will handle that
421 }
422 Tracker::SmartPointerSlice { vtable, .. } => {
423 // Free the slice builder
424 let builder_ptr = unsafe { self.data.assume_init() };
425 unsafe {
426 (vtable.free_fn)(builder_ptr);
427 }
428 }
429 Tracker::List { is_initialized, .. } => {
430 // Drop the initialized List
431 if *is_initialized {
432 if let Some(drop_fn) =
433 self.shape.vtable.sized().and_then(|v| (v.drop_in_place)())
434 {
435 unsafe { drop_fn(PtrMut::new(self.data.as_mut_byte_ptr())) };
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) =
446 self.shape.vtable.sized().and_then(|v| (v.drop_in_place)())
447 {
448 unsafe { drop_fn(PtrMut::new(self.data.as_mut_byte_ptr())) };
449 }
450 }
451
452 // Clean up any in-progress insertion state
453 match insert_state {
454 MapInsertState::PushingKey { key_ptr } => {
455 if let Some(key_ptr) = key_ptr {
456 // Deallocate the key buffer
457 if let Def::Map(map_def) = self.shape.def {
458 if let Ok(key_shape) = map_def.k().layout.sized_layout() {
459 if key_shape.size() > 0 {
460 unsafe {
461 alloc::alloc::dealloc(
462 key_ptr.as_mut_byte_ptr(),
463 key_shape,
464 )
465 };
466 }
467 }
468 }
469 }
470 }
471 MapInsertState::PushingValue { key_ptr, value_ptr } => {
472 // Drop and deallocate both key and value buffers
473 if let Def::Map(map_def) = self.shape.def {
474 // Drop and deallocate the key
475 if let Some(drop_fn) =
476 map_def.k().vtable.sized().and_then(|v| (v.drop_in_place)())
477 {
478 unsafe { drop_fn(PtrMut::new(key_ptr.as_mut_byte_ptr())) };
479 }
480 if let Ok(key_shape) = map_def.k().layout.sized_layout() {
481 if key_shape.size() > 0 {
482 unsafe {
483 alloc::alloc::dealloc(key_ptr.as_mut_byte_ptr(), key_shape)
484 };
485 }
486 }
487
488 // Drop and deallocate the value if it exists
489 if let Some(value_ptr) = value_ptr {
490 if let Ok(value_shape) = map_def.v().layout.sized_layout() {
491 if value_shape.size() > 0 {
492 unsafe {
493 alloc::alloc::dealloc(
494 value_ptr.as_mut_byte_ptr(),
495 value_shape,
496 )
497 };
498 }
499 }
500 }
501 }
502 }
503 MapInsertState::Idle => {}
504 }
505 }
506 Tracker::Option { building_inner } => {
507 // If we're building the inner value, it will be handled by the Option vtable
508 // No special cleanup needed here as the Option will either be properly
509 // initialized or remain uninitialized
510 if !building_inner {
511 // Option is fully initialized, drop it normally
512 if let Some(drop_fn) =
513 self.shape.vtable.sized().and_then(|v| (v.drop_in_place)())
514 {
515 unsafe { drop_fn(PtrMut::new(self.data.as_mut_byte_ptr())) };
516 }
517 }
518 }
519 }
520
521 self.tracker = Tracker::Uninit;
522 }
523
524 /// This must be called after (fully) initializing a value.
525 ///
526 /// This will most often result in a transition to [Tracker::Init] although
527 /// composite types (structs, enums, etc.) might be handled differently
528 ///
529 /// # Safety
530 ///
531 /// This should only be called when `self.data` has been actually initialized.
532 unsafe fn mark_as_init(&mut self) {
533 self.tracker = Tracker::Init;
534 }
535
536 /// Deallocate the memory associated with this frame, if it owns it.
537 ///
538 /// The memory has to be deinitialized first, see [Frame::deinit]
539 fn dealloc(self) {
540 if !matches!(self.tracker, Tracker::Uninit) {
541 unreachable!("a frame has to be deinitialized before being deallocated")
542 }
543
544 // Now, deallocate temporary String allocation if necessary
545 if let FrameOwnership::Owned = self.ownership {
546 if let Ok(layout) = self.shape.layout.sized_layout() {
547 if layout.size() > 0 {
548 unsafe { alloc::alloc::dealloc(self.data.as_mut_byte_ptr(), layout) };
549 }
550 }
551 // no need to update `self.ownership` since `self` drops at the end of this
552 }
553 }
554
555 /// Returns an error if the value is not fully initialized
556 fn require_full_initialization(&self) -> Result<(), ReflectError> {
557 match self.tracker {
558 Tracker::Uninit => Err(ReflectError::UninitializedValue { shape: self.shape }),
559 Tracker::Init => Ok(()),
560 Tracker::Array { iset, .. } => {
561 match self.shape.ty {
562 Type::Sequence(facet_core::SequenceType::Array(array_def)) => {
563 // Check if all array elements are initialized
564 if (0..array_def.n).all(|idx| iset.get(idx)) {
565 Ok(())
566 } else {
567 Err(ReflectError::UninitializedValue { shape: self.shape })
568 }
569 }
570 _ => Err(ReflectError::UninitializedValue { shape: self.shape }),
571 }
572 }
573 Tracker::Struct { iset, .. } => {
574 if iset.all_set() {
575 Ok(())
576 } else {
577 // Attempt to find the first uninitialized field, if possible
578 match self.shape.ty {
579 Type::User(UserType::Struct(struct_type)) => {
580 // Find index of the first bit not set
581 let first_missing_idx =
582 (0..struct_type.fields.len()).find(|&idx| !iset.get(idx));
583 if let Some(missing_idx) = first_missing_idx {
584 let field_name = struct_type.fields[missing_idx].name;
585 Err(ReflectError::UninitializedField {
586 shape: self.shape,
587 field_name,
588 })
589 } else {
590 // fallback, something went wrong
591 Err(ReflectError::UninitializedValue { shape: self.shape })
592 }
593 }
594 _ => Err(ReflectError::UninitializedValue { shape: self.shape }),
595 }
596 }
597 }
598 Tracker::Enum { variant, data, .. } => {
599 // Check if all fields of the variant are initialized
600 let num_fields = variant.data.fields.len();
601 if num_fields == 0 {
602 // Unit variant, always initialized
603 Ok(())
604 } else if (0..num_fields).all(|idx| data.get(idx)) {
605 Ok(())
606 } else {
607 // Find the first uninitialized field
608 let first_missing_idx = (0..num_fields).find(|&idx| !data.get(idx));
609 if let Some(missing_idx) = first_missing_idx {
610 let field_name = variant.data.fields[missing_idx].name;
611 Err(ReflectError::UninitializedEnumField {
612 shape: self.shape,
613 field_name,
614 variant_name: variant.name,
615 })
616 } else {
617 Err(ReflectError::UninitializedValue { shape: self.shape })
618 }
619 }
620 }
621 Tracker::SmartPointer { is_initialized } => {
622 if is_initialized {
623 Ok(())
624 } else {
625 Err(ReflectError::UninitializedValue { shape: self.shape })
626 }
627 }
628 Tracker::SmartPointerSlice { building_item, .. } => {
629 if building_item {
630 Err(ReflectError::UninitializedValue { shape: self.shape })
631 } else {
632 Ok(())
633 }
634 }
635 Tracker::List { is_initialized, .. } => {
636 if is_initialized {
637 Ok(())
638 } else {
639 Err(ReflectError::UninitializedValue { shape: self.shape })
640 }
641 }
642 Tracker::Map {
643 is_initialized,
644 insert_state,
645 } => {
646 if is_initialized && matches!(insert_state, MapInsertState::Idle) {
647 Ok(())
648 } else {
649 Err(ReflectError::UninitializedValue { shape: self.shape })
650 }
651 }
652 Tracker::Option { building_inner } => {
653 if building_inner {
654 Err(ReflectError::UninitializedValue { shape: self.shape })
655 } else {
656 Ok(())
657 }
658 }
659 }
660 }
661
662 /// Get the [EnumType] of the frame's shape, if it is an enum type
663 pub(crate) fn get_enum_type(&self) -> Result<EnumType, ReflectError> {
664 match self.shape.ty {
665 Type::User(UserType::Enum(e)) => Ok(e),
666 _ => Err(ReflectError::WasNotA {
667 expected: "enum",
668 actual: self.shape,
669 }),
670 }
671 }
672}
673
674impl<'facet> Drop for Partial<'facet> {
675 fn drop(&mut self) {
676 trace!("🧹 Partial is being dropped");
677
678 // We need to properly drop all initialized fields
679 while let Some(mut frame) = self.frames.pop() {
680 frame.deinit();
681
682 // Only deallocate if this frame owns the allocation
683 if let FrameOwnership::Owned = frame.ownership {
684 if let Ok(layout) = frame.shape.layout.sized_layout() {
685 if layout.size() > 0 {
686 unsafe { alloc::alloc::dealloc(frame.data.as_mut_byte_ptr(), layout) };
687 }
688 }
689 }
690 }
691 }
692}