Skip to main content

BufferBuilder

Struct BufferBuilder 

Source
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_size so 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 adds in_ptr to 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>

Source

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.

Source

pub fn write_int( &mut self, field_name: &str, value: i64, ) -> Result<(), BufferError>

Write a 64-bit signed integer to field_name.

Source

pub fn write_float( &mut self, field_name: &str, value: f64, ) -> Result<(), BufferError>

Write a 64-bit float to field_name.

Source

pub fn write_bool( &mut self, field_name: &str, value: bool, ) -> Result<(), BufferError>

Write a boolean to field_name. Encoded as 0u8 / 1u8.

Source

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.

Source

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.

Source

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).

Source

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).

Source

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.

Source

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).

Source

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.

Source

pub fn write_list_list_with<F>( &mut self, field_name: &str, inner_count: usize, encode_inner: F, ) -> Result<(), BufferError>
where F: FnMut(usize, &mut Vec<u8>) -> Result<(usize, usize), 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.

Source

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.

Source

pub fn write_list_record<'b, F>( &mut self, field_name: &str, elem_layout: &'b OffsetTable, elem_schema: &'b Schema, entries: &[F], ) -> Result<(), BufferError>
where F: Fn(&mut BufferBuilder<'b>) -> 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.

Source

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.

Source

pub fn finish(self) -> Vec<u8>

Consume the builder and return the underlying byte buffer.

Source

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.

Source

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.

Source

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§

Source§

impl<'a> Debug for BufferBuilder<'a>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

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> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.