pub struct BufferBuilder<'a> { /* private fields */ }Expand description
Type-checked writer over a record buffer with an optional tail area.
Phase 2.c shape:
- The fixed area is pre-allocated to
layout.root_sizeso every inline scalar slot is well-defined zero bytes per the spec. - String /
List<Int>writes append a[len: u32 LE][payload]record after the fixed area and back-patch the pointer slot in the fixed area with the tail-record’s byte offset (relative to the buffer start — the wasm side addsin_ptrto it).
Lifetime tie-in: the builder borrows the offset table so the same layout description can be reused for the matching reader without reparsing.
Implementations§
Source§impl<'a> BufferBuilder<'a>
impl<'a> BufferBuilder<'a>
Sourcepub fn new(layout: &'a OffsetTable, fields: &[Field]) -> Self
pub fn new(layout: &'a OffsetTable, fields: &[Field]) -> Self
Build a writer for layout with the byte buffer pre-zeroed to
layout.root_size.
fields carries the schema-level type info the layout pass
already validated; we keep a side index so the writer / reader
can detect a type mismatch without re-walking the schema.
Sourcepub fn write_int(
&mut self,
field_name: &str,
value: i64,
) -> Result<(), BufferError>
pub fn write_int( &mut self, field_name: &str, value: i64, ) -> Result<(), BufferError>
Write a 64-bit signed integer to field_name.
Sourcepub fn write_float(
&mut self,
field_name: &str,
value: f64,
) -> Result<(), BufferError>
pub fn write_float( &mut self, field_name: &str, value: f64, ) -> Result<(), BufferError>
Write a 64-bit float to field_name.
Sourcepub fn write_bool(
&mut self,
field_name: &str,
value: bool,
) -> Result<(), BufferError>
pub fn write_bool( &mut self, field_name: &str, value: bool, ) -> Result<(), BufferError>
Write a boolean to field_name. Encoded as 0u8 / 1u8.
Sourcepub fn write_unit(&mut self, field_name: &str) -> Result<(), BufferError>
pub fn write_unit(&mut self, field_name: &str) -> Result<(), BufferError>
Mark field_name as an internal unit slot. The slot is already zeroed by
new, so this is a no-op beyond the type check — useful to
surface a TypeMismatch early when the host accidentally
writes a unit marker to a non-unit slot.
Sourcepub fn write_value(
&mut self,
field_name: &str,
ty: &TypeRepr,
value: &Value,
) -> Result<(), BufferError>
pub fn write_value( &mut self, field_name: &str, ty: &TypeRepr, value: &Value, ) -> Result<(), BufferError>
Write any supported value shape into field_name using the declared
canonical type. This is the generic marshalling entry point used by
nested schemas, tuples, and the compiled backends for types that do not
have a dedicated write_* convenience method.
Sourcepub fn write_string(
&mut self,
field_name: &str,
value: &str,
) -> Result<(), BufferError>
pub fn write_string( &mut self, field_name: &str, value: &str, ) -> Result<(), BufferError>
Write a UTF-8 string into field_name’s tail-area record.
Appends [len: u32 LE][bytes] after the current buffer end,
padding the cursor up to 4 bytes first so the length prefix is
naturally aligned. The pointer slot in the fixed area is
back-patched with the byte offset of the length prefix
(relative to the buffer base — the wasm side adds in_ptr to
reach absolute memory).
Sourcepub fn write_list_int(
&mut self,
field_name: &str,
values: &[i64],
) -> Result<(), BufferError>
pub fn write_list_int( &mut self, field_name: &str, values: &[i64], ) -> Result<(), BufferError>
Write a List<Int> into field_name’s tail-area record.
Tail layout: [len: u32 LE][i64 LE x len]. The length prefix
is padded up to 8 bytes after itself so the i64 elements sit
on an 8-byte boundary the way the wasm side will eventually
expect (Phase 2.c keeps the elements untouched, but later
phases reading them need the alignment to be honest).
Sourcepub fn write_list_float(
&mut self,
field_name: &str,
values: &[f64],
) -> Result<(), BufferError>
pub fn write_list_float( &mut self, field_name: &str, values: &[f64], ) -> Result<(), BufferError>
Write a List<Float> into field_name’s tail-area record.
Phase 10-c: tail layout mirrors List<Int> — [len: u32 LE] [pad to 8][f64 LE x len]. The post-len pad keeps the f64
payload on an 8-byte boundary so the wasm side can issue
f64.load align=3 against the element stream.
Sourcepub fn write_list_bool(
&mut self,
field_name: &str,
values: &[bool],
) -> Result<(), BufferError>
pub fn write_list_bool( &mut self, field_name: &str, values: &[bool], ) -> Result<(), BufferError>
Write a List<Bool> into field_name’s tail-area record.
Phase 10-c: tail layout [len: u32 LE][u8 x len] — booleans
pack tightly with no inter-element padding per spec. The
record start is 4-byte aligned so the len prefix loads cleanly;
each element is one byte (0 for false, 1 for true).
Sourcepub fn write_list_string<S: AsRef<str>>(
&mut self,
field_name: &str,
values: &[S],
) -> Result<(), BufferError>
pub fn write_list_string<S: AsRef<str>>( &mut self, field_name: &str, values: &[S], ) -> Result<(), BufferError>
Write a List<String> into field_name’s tail-area record.
Phase 10-c: header [len: u32 LE][off_0: u32 LE]...[off_(n-1)]
followed by per-string [len: u32 LE][utf8 bytes] tail records.
Each off_i is the buffer-relative byte offset of the matching
String’s len prefix; the writer pads each String header to a
4-byte boundary so the reader can dereference without an
unaligned load.
Sourcepub fn write_list_list_with<F>(
&mut self,
field_name: &str,
inner_count: usize,
encode_inner: F,
) -> Result<(), BufferError>
pub fn write_list_list_with<F>( &mut self, field_name: &str, inner_count: usize, encode_inner: F, ) -> Result<(), BufferError>
Write a nested List<List<inner>> into field_name’s tail
area, where inner is an inline-fixed scalar element
(Int / Float / Bool).
Layout mirrors List<String> / List<Schema>: a header
[len: u32 LE][off_0]...[off_(n-1)] whose off_i are
buffer-relative offsets to per-element inner list records. Each
inner record is the same [len: u32 LE][payload] shape
Self::write_list_int / write_list_float / write_list_bool
produce, so an inner-record reader decodes them bit-identically.
The inner records carry no pointer slots of their own, so no
per-element relocation beyond the header’s off_i rebase is
needed when the buffer is later pasted into a parent.
encode_inner serialises one element’s payload bytes and returns
(element_count, inner_alignment); the caller drives it once per
inner list. Returns the count actually written.
Sourcepub fn list_record_writer<'b>(
&self,
field_name: &str,
elem_layout: &'b OffsetTable,
elem_schema: &'b Schema,
) -> Result<ListRecordWriter<'b>, BufferError>
pub fn list_record_writer<'b>( &self, field_name: &str, elem_layout: &'b OffsetTable, elem_schema: &'b Schema, ) -> Result<ListRecordWriter<'b>, BufferError>
Start writing a List<Schema> element. Returns a list-record
writer the caller drives one entry at a time; the actual list
header pointer is patched into the parent slot when
Self::finish_list_record is called.
Phase 10-c: each element is a branded sub-record (the inner
TypeRepr::Schema { schema }) whose fixed area lives in the
parent buffer’s tail area, addressed by a u32 entry in the
pointer array. The parent’s pointer slot in turn holds the
buffer-relative offset of the list header ([len: u32][off_0] ...).
Workflow:
let mut lw = parent.list_record(&field_name, &elem_layout, &elem_schema.fields)?;
for entry in entries {
let mut child = lw.start_entry(&parent_builder)?;
// ... write_int / write_string into `child` ...
lw.finish_entry(&mut parent_builder, child)?;
}
parent.finish_list_record(&field_name, lw)?;The split workflow keeps the parent buffer mutable for the
per-entry tail copy without aliasing the child borrow against
the parent’s field_index. Hosts that don’t need the full
step-by-step control can use the Self::write_list_record
convenience wrapper which takes a slice of pre-built dicts.
Sourcepub fn write_list_record<'b, F>(
&mut self,
field_name: &str,
elem_layout: &'b OffsetTable,
elem_schema: &'b Schema,
entries: &[F],
) -> Result<(), BufferError>
pub fn write_list_record<'b, F>( &mut self, field_name: &str, elem_layout: &'b OffsetTable, elem_schema: &'b Schema, entries: &[F], ) -> Result<(), BufferError>
Convenience writer for List<Schema> that builds each entry
from a pre-prepared Vec<(field_name, write_callback)> shape.
Phase 10-c: tests use the longer-form Self::list_record_writer
for full control; this wrapper accepts a list of buffer-builder
“actions” so simple cases don’t need to spell out the start /
finish dance.
Sourcepub fn finish_list_record(
&mut self,
writer: ListRecordWriter<'_>,
) -> Result<(), BufferError>
pub fn finish_list_record( &mut self, writer: ListRecordWriter<'_>, ) -> Result<(), BufferError>
Commit a ListRecordWriter — emit the list header into the
tail area (aligned to 4) and patch the field’s pointer slot
with the header offset.
Sourcepub fn finish_arena_absolute(
self,
arena_base: u32,
) -> Result<Vec<u8>, BufferError>
pub fn finish_arena_absolute( self, arena_base: u32, ) -> Result<Vec<u8>, BufferError>
Consume the builder and return the byte buffer with every
pointer slot rebased from buffer-relative to
arena-absolute by adding arena_base (the absolute arena
offset the buffer is about to be copied to — i.e. in_ptr).
F1 unifies the in-buffer pointer convention on a single
arena-absolute basis: the input marshaller knows in_ptr at this
point, so it bakes it into every slot here once, and the machine
code’s param-read drops its old + in_ptr rebase (the slot is
already arena-absolute). The same recursive walk
[finish_sub_record] uses to paste a child into a parent applies
— a rebase by arena_base is structurally identical to a paste at
arena_base, relocating every nested-schema and pointer-array
entry too. arena_base == 0 is a no-op (the slots are already
correct), so a zero-const-data layout stays byte-identical.
Sourcepub fn sub_record<'b>(
&mut self,
field_name: &str,
sub_layout: &'b OffsetTable,
sub_fields: &[Field],
) -> Result<BufferBuilder<'b>, BufferError>
pub fn sub_record<'b>( &mut self, field_name: &str, sub_layout: &'b OffsetTable, sub_fields: &[Field], ) -> Result<BufferBuilder<'b>, BufferError>
Allocate a nested branded sub-record under field_name and
return a detached child BufferBuilder sized to the sub
schema’s fixed area.
Phase 9.b-1: mirrors BufferReader::sub_record on the writer
side so a host can pack Schema-typed #main args without
reaching for hand-rolled byte arithmetic. The returned builder
is detached — it owns its own Vec<u8> pre-sized to
sub_layout.root_size. The parent’s pointer slot stays zero
until the caller hands the child back via
Self::finish_sub_record, which appends the child’s bytes to
the parent’s tail area (aligning to sub_layout.root_align) and
back-patches the slot.
Detached children keep the writer simple: they don’t borrow the parent (so multiple sibling sub-records can be authored independently), and the parent has a single commit step that also enforces the field-name → pointer-slot binding the layout pass guarantees.
Sourcepub fn finish_sub_record(
&mut self,
field_name: &str,
child: BufferBuilder<'_>,
) -> Result<(), BufferError>
pub fn finish_sub_record( &mut self, field_name: &str, child: BufferBuilder<'_>, ) -> Result<(), BufferError>
Commit a detached sub-record produced by Self::sub_record.
Appends the child’s byte buffer to the parent’s tail area
(padded up to the sub schema’s root alignment) and writes the
resulting buffer-relative offset into the parent’s pointer slot
for field_name. The child is consumed.
Pointer relocation: the child built its String / List<Int> /
nested-Schema slots with offsets relative to the child’s
buffer base (0). Once the child is pasted into the parent at
sub_base, every such pointer slot needs + sub_base to become
parent-relative again — otherwise the wasm side / reader walks
the wrong bytes. We walk the child’s field layout recursively
and rewrite each u32 pointer in place before appending.
Errors mirror the parent’s other writers: an unknown field name
or a type-shape mismatch surfaces before any bytes are moved.
An oversized child (offset doesn’t fit in u32) surfaces as
BufferError::ValueTooLarge.
Trait Implementations§
Auto Trait Implementations§
impl<'a> Freeze for BufferBuilder<'a>
impl<'a> RefUnwindSafe for BufferBuilder<'a>
impl<'a> Send for BufferBuilder<'a>
impl<'a> Sync for BufferBuilder<'a>
impl<'a> Unpin for BufferBuilder<'a>
impl<'a> UnsafeUnpin for BufferBuilder<'a>
impl<'a> UnwindSafe for BufferBuilder<'a>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more