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, 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.drop_in_place {
358 unsafe { drop_fn(self.data.assume_init()) };
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) = array_def.t.vtable.drop_in_place {
371 unsafe { drop_fn(element_ptr) };
372 }
373 }
374 }
375 }
376 }
377 }
378 Tracker::Struct { iset, .. } => {
379 // Drop initialized struct fields
380 if let Type::User(UserType::Struct(struct_type)) = self.shape.ty {
381 for (idx, field) in struct_type.fields.iter().enumerate() {
382 if iset.get(idx) {
383 // This field was initialized, drop it
384 let field_ptr = unsafe { self.data.field_init_at(field.offset) };
385 if let Some(drop_fn) = field.shape().vtable.drop_in_place {
386 unsafe { drop_fn(field_ptr) };
387 }
388 }
389 }
390 }
391 }
392 Tracker::Enum { variant, data, .. } => {
393 // Drop initialized enum variant fields
394 for (idx, field) in variant.data.fields.iter().enumerate() {
395 if data.get(idx) {
396 // This field was initialized, drop it
397 let field_ptr = unsafe { self.data.as_mut_byte_ptr().add(field.offset) };
398 if let Some(drop_fn) = field.shape().vtable.drop_in_place {
399 unsafe { drop_fn(PtrMut::new(NonNull::new_unchecked(field_ptr))) };
400 }
401 }
402 }
403 }
404 Tracker::SmartPointer { is_initialized } => {
405 // Drop the initialized Box
406 if *is_initialized {
407 if let Some(drop_fn) = self.shape.vtable.drop_in_place {
408 unsafe { drop_fn(self.data.assume_init()) };
409 }
410 }
411 // Note: we don't deallocate the inner value here because
412 // the Box's drop will handle that
413 }
414 Tracker::SmartPointerSlice { vtable, .. } => {
415 // Free the slice builder
416 let builder_ptr = unsafe { self.data.assume_init() };
417 unsafe {
418 (vtable.free_fn)(builder_ptr);
419 }
420 }
421 Tracker::List { is_initialized, .. } => {
422 // Drop the initialized List
423 if *is_initialized {
424 if let Some(drop_fn) = self.shape.vtable.drop_in_place {
425 unsafe { drop_fn(self.data.assume_init()) };
426 }
427 }
428 }
429 Tracker::Map {
430 is_initialized,
431 insert_state,
432 } => {
433 // Drop the initialized Map
434 if *is_initialized {
435 if let Some(drop_fn) = self.shape.vtable.drop_in_place {
436 unsafe { drop_fn(self.data.assume_init()) };
437 }
438 }
439
440 // Clean up any in-progress insertion state
441 match insert_state {
442 MapInsertState::PushingKey { key_ptr } => {
443 if let Some(key_ptr) = key_ptr {
444 // Deallocate the key buffer
445 if let Def::Map(map_def) = self.shape.def {
446 if let Ok(key_shape) = map_def.k().layout.sized_layout() {
447 if key_shape.size() > 0 {
448 unsafe {
449 alloc::alloc::dealloc(
450 key_ptr.as_mut_byte_ptr(),
451 key_shape,
452 )
453 };
454 }
455 }
456 }
457 }
458 }
459 MapInsertState::PushingValue { key_ptr, value_ptr } => {
460 // Drop and deallocate both key and value buffers
461 if let Def::Map(map_def) = self.shape.def {
462 // Drop and deallocate the key
463 if let Some(drop_fn) = map_def.k().vtable.drop_in_place {
464 unsafe { drop_fn(key_ptr.assume_init()) };
465 }
466 if let Ok(key_shape) = map_def.k().layout.sized_layout() {
467 if key_shape.size() > 0 {
468 unsafe {
469 alloc::alloc::dealloc(key_ptr.as_mut_byte_ptr(), key_shape)
470 };
471 }
472 }
473
474 // Drop and deallocate the value if it exists
475 if let Some(value_ptr) = value_ptr {
476 if let Ok(value_shape) = map_def.v().layout.sized_layout() {
477 if value_shape.size() > 0 {
478 unsafe {
479 alloc::alloc::dealloc(
480 value_ptr.as_mut_byte_ptr(),
481 value_shape,
482 )
483 };
484 }
485 }
486 }
487 }
488 }
489 MapInsertState::Idle => {}
490 }
491 }
492 Tracker::Option { building_inner } => {
493 // If we're building the inner value, it will be handled by the Option vtable
494 // No special cleanup needed here as the Option will either be properly
495 // initialized or remain uninitialized
496 if !building_inner {
497 // Option is fully initialized, drop it normally
498 if let Some(drop_fn) = self.shape.vtable.drop_in_place {
499 unsafe { drop_fn(self.data.assume_init()) };
500 }
501 }
502 }
503 }
504
505 self.tracker = Tracker::Uninit;
506 }
507
508 /// This must be called after (fully) initializing a value.
509 ///
510 /// This will most often result in a transition to [Tracker::Init] although
511 /// composite types (structs, enums, etc.) might be handled differently
512 ///
513 /// # Safety
514 ///
515 /// This should only be called when `self.data` has been actually initialized.
516 unsafe fn mark_as_init(&mut self) {
517 self.tracker = Tracker::Init;
518 }
519
520 /// Deallocate the memory associated with this frame, if it owns it.
521 ///
522 /// The memory has to be deinitialized first, see [Frame::deinit]
523 fn dealloc(self) {
524 if !matches!(self.tracker, Tracker::Uninit) {
525 unreachable!("a frame has to be deinitialized before being deallocated")
526 }
527
528 // Now, deallocate temporary String allocation if necessary
529 if let FrameOwnership::Owned = self.ownership {
530 if let Ok(layout) = self.shape.layout.sized_layout() {
531 if layout.size() > 0 {
532 unsafe { alloc::alloc::dealloc(self.data.as_mut_byte_ptr(), layout) };
533 }
534 }
535 // no need to update `self.ownership` since `self` drops at the end of this
536 }
537 }
538
539 /// Returns an error if the value is not fully initialized
540 fn require_full_initialization(&self) -> Result<(), ReflectError> {
541 match self.tracker {
542 Tracker::Uninit => Err(ReflectError::UninitializedValue { shape: self.shape }),
543 Tracker::Init => Ok(()),
544 Tracker::Array { iset, .. } => {
545 match self.shape.ty {
546 Type::Sequence(facet_core::SequenceType::Array(array_def)) => {
547 // Check if all array elements are initialized
548 if (0..array_def.n).all(|idx| iset.get(idx)) {
549 Ok(())
550 } else {
551 Err(ReflectError::UninitializedValue { shape: self.shape })
552 }
553 }
554 _ => Err(ReflectError::UninitializedValue { shape: self.shape }),
555 }
556 }
557 Tracker::Struct { iset, .. } => {
558 if iset.all_set() {
559 Ok(())
560 } else {
561 // Attempt to find the first uninitialized field, if possible
562 match self.shape.ty {
563 Type::User(UserType::Struct(struct_type)) => {
564 // Find index of the first bit not set
565 let first_missing_idx =
566 (0..struct_type.fields.len()).find(|&idx| !iset.get(idx));
567 if let Some(missing_idx) = first_missing_idx {
568 let field_name = struct_type.fields[missing_idx].name;
569 Err(ReflectError::UninitializedField {
570 shape: self.shape,
571 field_name,
572 })
573 } else {
574 // fallback, something went wrong
575 Err(ReflectError::UninitializedValue { shape: self.shape })
576 }
577 }
578 _ => Err(ReflectError::UninitializedValue { shape: self.shape }),
579 }
580 }
581 }
582 Tracker::Enum { variant, data, .. } => {
583 // Check if all fields of the variant are initialized
584 let num_fields = variant.data.fields.len();
585 if num_fields == 0 {
586 // Unit variant, always initialized
587 Ok(())
588 } else if (0..num_fields).all(|idx| data.get(idx)) {
589 Ok(())
590 } else {
591 // Find the first uninitialized field
592 let first_missing_idx = (0..num_fields).find(|&idx| !data.get(idx));
593 if let Some(missing_idx) = first_missing_idx {
594 let field_name = variant.data.fields[missing_idx].name;
595 Err(ReflectError::UninitializedEnumField {
596 shape: self.shape,
597 field_name,
598 variant_name: variant.name,
599 })
600 } else {
601 Err(ReflectError::UninitializedValue { shape: self.shape })
602 }
603 }
604 }
605 Tracker::SmartPointer { is_initialized } => {
606 if is_initialized {
607 Ok(())
608 } else {
609 Err(ReflectError::UninitializedValue { shape: self.shape })
610 }
611 }
612 Tracker::SmartPointerSlice { building_item, .. } => {
613 if building_item {
614 Err(ReflectError::UninitializedValue { shape: self.shape })
615 } else {
616 Ok(())
617 }
618 }
619 Tracker::List { is_initialized, .. } => {
620 if is_initialized {
621 Ok(())
622 } else {
623 Err(ReflectError::UninitializedValue { shape: self.shape })
624 }
625 }
626 Tracker::Map {
627 is_initialized,
628 insert_state,
629 } => {
630 if is_initialized && matches!(insert_state, MapInsertState::Idle) {
631 Ok(())
632 } else {
633 Err(ReflectError::UninitializedValue { shape: self.shape })
634 }
635 }
636 Tracker::Option { building_inner } => {
637 if building_inner {
638 Err(ReflectError::UninitializedValue { shape: self.shape })
639 } else {
640 Ok(())
641 }
642 }
643 }
644 }
645
646 /// Get the [EnumType] of the frame's shape, if it is an enum type
647 pub(crate) fn get_enum_type(&self) -> Result<EnumType, ReflectError> {
648 match self.shape.ty {
649 Type::User(UserType::Enum(e)) => Ok(e),
650 _ => Err(ReflectError::WasNotA {
651 expected: "enum",
652 actual: self.shape,
653 }),
654 }
655 }
656}
657
658impl<'facet> Drop for Partial<'facet> {
659 fn drop(&mut self) {
660 trace!("🧹 Partial is being dropped");
661
662 // We need to properly drop all initialized fields
663 while let Some(mut frame) = self.frames.pop() {
664 frame.deinit();
665
666 // Only deallocate if this frame owns the allocation
667 if let FrameOwnership::Owned = frame.ownership {
668 if let Ok(layout) = frame.shape.layout.sized_layout() {
669 if layout.size() > 0 {
670 unsafe { alloc::alloc::dealloc(frame.data.as_mut_byte_ptr(), layout) };
671 }
672 }
673 }
674 }
675 }
676}