Skip to content

Refactor serialization to avoid reversing or collecting iterators #641

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 4 additions & 25 deletions facet-reflect/src/peek/enum_.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use facet_core::{EnumRepr, EnumType, Field, Shape, UserType, Variant};
use facet_core::{EnumRepr, EnumType, Shape, UserType, Variant};

use crate::{Peek, trace};

use super::HasFields;
use super::{FieldIter, HasFields};

/// Lets you read from an enum (implements read-only enum operations)
#[derive(Clone, Copy)]
Expand Down Expand Up @@ -243,29 +243,8 @@ impl<'mem, 'facet, 'shape> PeekEnum<'mem, 'facet, 'shape> {
}

impl<'mem, 'facet, 'shape> HasFields<'mem, 'facet, 'shape> for PeekEnum<'mem, 'facet, 'shape> {
fn fields(
&self,
) -> impl DoubleEndedIterator<Item = (Field<'shape>, Peek<'mem, 'facet, 'shape>)> {
// Get the active variant and its fields
let variant = match self.active_variant() {
Ok(v) => v,
Err(e) => panic!("Cannot get active variant: {:?}", e),
};
let fields = &variant.data.fields;

// Create an iterator that yields the field definition and field value
(0..fields.len()).filter_map(move |i| {
// Get the field definition
let field = fields[i];
// Get the field value
let field_value = match self.field(i) {
Ok(Some(v)) => v,
Ok(None) => return None,
Err(e) => panic!("Cannot get field: {:?}", e),
};
// Return the field definition and value
Some((field, field_value))
})
fn fields(&self) -> FieldIter<'mem, 'facet, 'shape> {
FieldIter::new_enum(*self)
}
}

Expand Down
207 changes: 207 additions & 0 deletions facet-reflect/src/peek/fields.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
use core::ops::Range;

use facet_core::{Field, FieldFlags};

use crate::Peek;
use alloc::{vec, vec::Vec};

use super::{PeekEnum, PeekStruct, PeekTuple};

/// Trait for types that have field methods
///
/// This trait allows code to be written generically over both structs and enums
/// that provide field access and iteration capabilities.
pub trait HasFields<'mem, 'facet, 'shape> {
/// Iterates over all fields in this type, providing both field metadata and value
fn fields(&self) -> FieldIter<'mem, 'facet, 'shape>;

/// Iterates over fields in this type that should be included when it is serialized
fn fields_for_serialize(&self) -> FieldsForSerializeIter<'mem, 'facet, 'shape> {
FieldsForSerializeIter {
stack: vec![self.fields()],
}
}
}

/// An iterator over all the fields of a struct or enum. See [`HasFields::fields`]
pub struct FieldIter<'mem, 'facet, 'shape> {
state: FieldIterState<'mem, 'facet, 'shape>,
range: Range<usize>,
}

enum FieldIterState<'mem, 'facet, 'shape> {
Struct(PeekStruct<'mem, 'facet, 'shape>),
Tuple(PeekTuple<'mem, 'facet, 'shape>),
Enum {
peek_enum: PeekEnum<'mem, 'facet, 'shape>,
fields: &'shape [Field<'shape>],
},
FlattenedEnum {
field: Field<'shape>,
value: Peek<'mem, 'facet, 'shape>,
},
}

impl<'mem, 'facet, 'shape> FieldIter<'mem, 'facet, 'shape> {
pub(crate) fn new_struct(struct_: PeekStruct<'mem, 'facet, 'shape>) -> Self {
Self {
range: 0..struct_.ty.fields.len(),
state: FieldIterState::Struct(struct_),
}
}

pub(crate) fn new_enum(enum_: PeekEnum<'mem, 'facet, 'shape>) -> Self {
// Get the fields of the active variant
let variant = match enum_.active_variant() {
Ok(v) => v,
Err(e) => panic!("Cannot get active variant: {:?}", e),
};
let fields = &variant.data.fields;

Self {
range: 0..fields.len(),
state: FieldIterState::Enum {
peek_enum: enum_,
fields,
},
}
}

pub(crate) fn new_tuple(tuple: PeekTuple<'mem, 'facet, 'shape>) -> Self {
Self {
range: 0..tuple.len(),
state: FieldIterState::Tuple(tuple),
}
}

fn get_field_by_index(
&self,
index: usize,
) -> Option<(Field<'shape>, Peek<'mem, 'facet, 'shape>)> {
match self.state {
FieldIterState::Struct(peek_struct) => {
let field = peek_struct.ty.fields.get(index).copied()?;
let value = peek_struct.field(index).ok()?;
Some((field, value))
}
FieldIterState::Tuple(peek_tuple) => {
let field = peek_tuple.ty.fields.get(index).copied()?;
let value = peek_tuple.field(index)?;
Some((field, value))
}
FieldIterState::Enum { peek_enum, fields } => {
// Get the field definition
let field = fields[index];
// Get the field value
let field_value = match peek_enum.field(index) {
Ok(Some(v)) => v,
Ok(None) => return None,
Err(e) => panic!("Cannot get field: {:?}", e),
};
// Return the field definition and value
Some((field, field_value))
}
FieldIterState::FlattenedEnum { field, value } => {
if index == 0 {
Some((field, value))
} else {
None
}
}
}
}
}

impl<'mem, 'facet, 'shape> Iterator for FieldIter<'mem, 'facet, 'shape> {
type Item = (Field<'shape>, Peek<'mem, 'facet, 'shape>);

fn next(&mut self) -> Option<Self::Item> {
loop {
let index = self.range.next()?;

let Some(field) = self.get_field_by_index(index) else {
continue;
};

return Some(field);
}
}

fn size_hint(&self) -> (usize, Option<usize>) {
self.range.size_hint()
}
}

impl DoubleEndedIterator for FieldIter<'_, '_, '_> {
fn next_back(&mut self) -> Option<Self::Item> {
loop {
let index = self.range.next_back()?;

let Some(field) = self.get_field_by_index(index) else {
continue;
};

return Some(field);
}
}
}

impl ExactSizeIterator for FieldIter<'_, '_, '_> {}

/// An iterator over the fields of a struct or enum that should be serialized. See [`HasFields::fields_for_serialize`]
pub struct FieldsForSerializeIter<'mem, 'facet, 'shape> {
stack: Vec<FieldIter<'mem, 'facet, 'shape>>,
}
Comment on lines +152 to +154
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type is a hand-written iterator to re-create the old fields_for_serialize() impl Trait method. This was necessary so that facet-serialize could store a nameable iterator type (I also tried Box<dyn Iterator<...>> but hit some weird lifetime bounds issues... plus I didn't feel great about needing an extra box for this

(One minor plus to this implementation is that it's no longer recursive!)


impl<'mem, 'facet, 'shape> Iterator for FieldsForSerializeIter<'mem, 'facet, 'shape> {
type Item = (Field<'shape>, Peek<'mem, 'facet, 'shape>);

fn next(&mut self) -> Option<Self::Item> {
loop {
let mut fields = self.stack.pop()?;
let Some((mut field, peek)) = fields.next() else {
continue;
};
self.stack.push(fields);

let should_skip = unsafe { field.should_skip_serializing(peek.data()) };
if should_skip {
continue;
}

if field.flags.contains(FieldFlags::FLATTEN) && !field.flattened {
if let Ok(struct_peek) = peek.into_struct() {
self.stack.push(FieldIter::new_struct(struct_peek))
} else if let Ok(enum_peek) = peek.into_enum() {
// normally we'd serialize to something like:
//
// {
// "field_on_struct": {
// "VariantName": { "field_on_variant": "foo" }
// }
// }
//
// But since `field_on_struct` is flattened, instead we do:
//
// {
// "VariantName": { "field_on_variant": "foo" }
// }
field.name = enum_peek
.active_variant()
.expect("Failed to get active variant")
.name;
field.flattened = true;
self.stack.push(FieldIter {
range: 0..1,
state: FieldIterState::FlattenedEnum { field, value: peek },
});
} else {
// TODO: fail more gracefully
panic!("cannot flatten a {}", field.shape())
}
} else {
return Some((field, peek));
}
}
}
}
3 changes: 3 additions & 0 deletions facet-reflect/src/peek/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ pub use struct_::*;
mod enum_;
pub use enum_::*;

mod fields;
pub use fields::*;

mod list;
pub use list::*;

Expand Down
72 changes: 5 additions & 67 deletions facet-reflect/src/peek/struct_.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use facet_core::{Field, FieldError, FieldFlags, StructType};
use facet_core::{FieldError, StructType};

use crate::Peek;
use alloc::{vec, vec::Vec};

use super::{FieldIter, HasFields};

/// Lets you read from a struct (implements read-only struct operations)
#[derive(Clone, Copy)]
Expand Down Expand Up @@ -63,70 +64,7 @@ impl<'mem, 'facet, 'shape> PeekStruct<'mem, 'facet, 'shape> {
impl<'mem, 'facet, 'shape> HasFields<'mem, 'facet, 'shape> for PeekStruct<'mem, 'facet, 'shape> {
/// Iterates over all fields in this struct, providing both name and value
#[inline]
fn fields(
&self,
) -> impl DoubleEndedIterator<Item = (Field<'shape>, Peek<'mem, 'facet, 'shape>)> {
(0..self.field_count()).filter_map(|i| {
let field = self.ty.fields.get(i).copied()?;
let value = self.field(i).ok()?;
Some((field, value))
})
}
}

/// Trait for types that have field methods
///
/// This trait allows code to be written generically over both structs and enums
/// that provide field access and iteration capabilities.
pub trait HasFields<'mem, 'facet, 'shape> {
/// Iterates over all fields in this type, providing both field metadata and value
fn fields(
&self,
) -> impl DoubleEndedIterator<Item = (Field<'shape>, Peek<'mem, 'facet, 'shape>)>;

/// Iterates over fields in this type that should be included when it is serialized
fn fields_for_serialize(
&self,
) -> impl DoubleEndedIterator<Item = (Field<'shape>, Peek<'mem, 'facet, 'shape>)> {
// This is a default implementation that filters out fields with `skip_serializing`
// attribute and handles field flattening.
self.fields()
.filter(|(field, peek)| !unsafe { field.should_skip_serializing(peek.data()) })
.flat_map(move |(mut field, peek)| {
if field.flags.contains(FieldFlags::FLATTEN) {
let mut flattened = Vec::new();
if let Ok(struct_peek) = peek.into_struct() {
struct_peek
.fields_for_serialize()
.for_each(|item| flattened.push(item));
} else if let Ok(enum_peek) = peek.into_enum() {
// normally we'd serialize to something like:
//
// {
// "field_on_struct": {
// "VariantName": { "field_on_variant": "foo" }
// }
// }
//
// But since `field_on_struct` is flattened, instead we do:
//
// {
// "VariantName": { "field_on_variant": "foo" }
// }
field.name = enum_peek
.active_variant()
.expect("Failed to get active variant")
.name;
field.flattened = true;
flattened.push((field, peek));
} else {
// TODO: fail more gracefully
panic!("cannot flatten a {}", field.shape())
}
flattened
} else {
vec![(field, peek)]
}
})
fn fields(&self) -> FieldIter<'mem, 'facet, 'shape> {
FieldIter::new_struct(*self)
}
}
6 changes: 3 additions & 3 deletions facet-reflect/src/peek/tuple.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use core::fmt::Debug;
use facet_core::TupleType;

use super::Peek;
use super::{FieldIter, Peek};

/// Field index and associated peek value
pub type TupleField<'mem, 'facet, 'shape> = (usize, Peek<'mem, 'facet, 'shape>);
Expand Down Expand Up @@ -50,8 +50,8 @@ impl<'mem, 'facet, 'shape> PeekTuple<'mem, 'facet, 'shape> {
}

/// Iterate over all fields
pub fn fields(&self) -> impl DoubleEndedIterator<Item = TupleField<'mem, 'facet, 'shape>> + '_ {
(0..self.len()).filter_map(|i| self.field(i).map(|p| (i, p)))
pub fn fields(&self) -> FieldIter<'mem, 'facet, 'shape> {
FieldIter::new_tuple(*self)
}

/// Type information
Expand Down
Loading