rkyv

Safe MoonBit reader and small encoder for rkyv 0.8 archives

rkyv
serialization
archive
interoperability
moon add mizchi/rkyv@0.5.0
Download zip
Author
Version
0.5.0
License
Apache-2.0
Last updated
20 days ago
Downloads
13
README

#mizchi/rkyv

A small, safe MoonBit runtime and encoder for rkyv 0.8 archives. It reads a Rust-produced archive without copying it, while keeping schema-specific code in generated bindings or application code.

The design follows the split used by cometkim/rkyv-js: a shared wire-format runtime plus bindings generated from Rust types.

#Release contract

The supported wire-format contract is rkyv 0.8.17 with its default format:

  • little-endian;
  • aligned archives; and
  • 32-bit relative pointers and archived usize values.

The executable compatibility contract covers the checked reader primitives, host-defined scalar/vector/option/tagged-union schemas, and the generated catalog bindings. See conformance/README.md for the exact fixtures and limits.

#Features

  • Bounds-checked reader for the default rkyv format.
  • Bit-compatible u8u64, i8i64, f32, f64, and bool reads.
  • Relative pointers, ArchivedVec<T> headers, and ArchivedOption<T> value offsets.
  • A bounds-checked, zero-copy BytesView for raw archive ranges.
  • A zero-copy ArchivedVec<u32> view with lazy access, Array/ FixedArray materialization, and caller-owned buffer copies.
  • Default-format schema-directed encoders for primitives, strings, vectors, options, structs, and explicit tagged unions.
  • Little- and big-endian primitive readers with 16-, 32-, or 64-bit pointers.
  • Experimental Rust code generation for typed MoonBit views of named structs.
  • Experimental MoonBit-owned schemas for Rust-free default-format encoding and schema-directed zero-copy views, including the JavaScript target.
  • Optional RMBT v1 envelopes with format flags, payload length, CRC-32, and a caller-provided payload limit for untrusted transport data.

#Usage

import {
"mizchi/rkyv" @rkyv,
}

let reader = @rkyv.Reader::new(archive_bytes)
let view = reader.read_vec_u32(archive_bytes.length() - 8) catch {
error => abort("invalid rkyv archive: \{error}")
}

All fallible reader and generated-view APIs raise RkyvError. View::root is the lazy path for trusted archives. For an untrusted archive generated Rust views also provide View::validate(bytes): it traverses every supported field, checks pointers, collection spans, UTF-8, canonical bool/Option/enum tags, and limits recursive traversal to 256 levels before returning the same zero-copy view. It is deliberately scoped to the generated supported types and is not a drop-in replacement for Rust bytecheck on arbitrary rkyv types.

#Transport envelope

For bytes received from a file, network, or cache, wrap the ordinary rkyv payload in an optional RMBT v1 envelope. It records the selected rkyv format, payload length, and CRC-32; decode_envelope_with_limit rejects an excessive declared payload before copying it. After checking the envelope, pass its payload to the generated validator.

///|
let envelope = @rkyv.encode_envelope(archive)

///|
let decoded = @rkyv.decode_envelope_with_limit(envelope, 16 * 1024 * 1024) catch {
error => abort("invalid archive transport: \{error}")
}

///|
let view = @catalog.CatalogView::validate(decoded.payload_bytes()) catch {
error => abort("invalid rkyv archive: \{error}")
}

The primary collection APIs are read_vec_u32, read_vec_u32_length, read_vec_u32_into, and U32VecView::copy_into. They validate first and do not allocate an error wrapper on a successful read:

///|
let view = reader.read_vec_u32(root_offset) catch {
error => abort("invalid rkyv archive: \{error}")
}

///|
let selected = view.get(index)

Use try/catch when a caller needs local recovery. U32VecView::get and U32VecView::at return None only for an out-of-range logical index; archive validation failures always raise RkyvError.

reader.read_bytes_view(offset, length) returns a zero-copy BytesView after checking only the requested range. It does not validate a higher-level rkyv layout or UTF-8; use read_string when a decoded, validated String is needed.

#Generated MoonBit schemas and JavaScript

Use an .mbtx script as the source of truth, generate a typed MoonBit package, then import that package from the client. This keeps the dynamic Schema / Value representation out of application code while still using no Rust.

#Direct host-defined schema API

For a small or one-off archive, define the schema directly in MoonBit. The field declaration order in Schema::Struct is the archive layout contract. Value::Struct checks both the field count and names before encoding, and raises SchemaError if it does not match the schema.

This direct form is runtime-checked because field names and schemas are values. Use the generated API below when callers need compile-time field and value types.

///|
let user = @rkyv.Schema::Struct([
{ name: "id", schema: @rkyv.Schema::U32 },
{ name: "active", schema: @rkyv.Schema::Bool },
{ name: "name", schema: @rkyv.Schema::String },
{ name: "scores", schema: @rkyv.Schema::VecU32 },
])

///|
let archive = user.encode(
@rkyv.Value::Struct([
{ name: "id", value: @rkyv.Value::U32(42U) },
{ name: "active", value: @rkyv.Value::Bool(true) },
{ name: "name", value: @rkyv.Value::String("Ada") },
{ name: "scores", value: @rkyv.Value::VecU32([7U, 11U, 13U]) },
]),
) catch {
_ => abort("schema and value do not match")
}

root opens a schema-directed zero-copy View. Accessing the vector does not materialize an Array unless the caller asks for one:

///|
let root = user.root(archive) catch { _ => abort("invalid archive") }

///|
let name = root.field("name")

///|
let scores = root.field("scores")

#In-place archive updates

Use encode_mut when the archive buffer must remain caller-owned. root_mut validates the root and provides MutView setters that never relocate data: set_u32, set_bool, set_string with the same UTF-8 byte length, and vec_u32_mut().set for an existing vector index. Changing a string or vector length still requires re-encoding the archive.

///|
let archive = user.encode_mut(
@rkyv.Value::Struct([
{ name: "id", value: @rkyv.Value::U32(42U) },
{ name: "active", value: @rkyv.Value::Bool(true) },
{ name: "name", value: @rkyv.Value::String("Ada") },
{ name: "scores", value: @rkyv.Value::VecU32([7U, 11U, 13U]) },
]),
)

///|
let root = user.root_mut(archive.mut_view())

///|
match root.field("id") {
Some(id) => ignore(id.set_u32(99U))
None => abort("missing id")
}

///|
match root.field("scores") {
Some(scores) => match scores.vec_u32_mut() {
Some(scores) => ignore(scores.set(1, 42U))
None => abort("scores is not Vec<u32>")
}
None => abort("missing scores")
}

///|
let user = {
name: "User",
fields: [
{ name: "id", typ: UInt32 },
{ name: "active", typ: Bool },
{ name: "name", typ: Text },
{ name: "scores", typ: UInt32Array },
],
}
emit_schema(user)

The JavaScript client imports only the generated API. It receives schema-specific UserView and UserMutView types: callers cannot pass a field name, and the type of every getter and setter is fixed by the source schema. encode always returns caller-owned Array[Byte]; convert it explicitly with Bytes::from_array when opening a read-only view.

///|
import {
"mizchi/rkyv/examples/host_codegen/generated" @user,
}

///|
let archive = @user.encode(42U, true, "Ada", [7U, 11U, 13U])

///|
let root = @user.UserView::root(Bytes::from_array(archive))

///|
let name : String = root.name()

///|
let scores : @rkyv.U32VecView = root.scores()

For an in-place update, the generated mutable view exposes only valid setters:

///|
let archive = @user.encode(42U, true, "Ada", [7U, 11U, 13U])

///|
let writer = @user.UserMutView::root(archive.mut_view())

///|
writer.set_id(99U)
writer.set_active(false)
let patched : Bool = writer.set_name_same_length("Eve")
let scores = writer.scores_mut()
ignore(scores.set(1, 42U))

Regenerate after changing the source schema. host-js first confirms that the checked-in generated source is current, then runs the client entirely under the JS target:

just host-schema just host-js # Eve: 3 scores

The current generator supports u32, bool, String, and Vec<u32> fields. Field declaration order is the layout contract. It does not infer arbitrary Rust Archived<T> field offsets; for a schema shared with an existing Rust type, verify its bytes against Rust or use the Rust layout codegen path.

#Dogfooding: static product catalog

examples/catalog is an end-to-end, production-shaped example. A Rust build step owns Catalog { products: Vec<Product> }, archives real catalog data with rkyv, and uses RkyvMbt to generate the exact MoonBit views. The JavaScript-target client opens the checked-in .rkyv binary through a minimal Node Buffer -> Bytes loader, then searches the Vec<Product> lazily without parsing JSON or materializing a product array.

The generated package also contains ProductInput and CatalogInput. Their constructors make the MoonBit-to-Rust path type-safe for this schema, including Vec<Product>, Vec<String>, and Option<u32>.

just catalog-generate moon test --target js examples/catalog/client just catalog-js # Moon Mug: 1800 cents just catalog-roundtrip

check-catalog, which is included in just conformance, verifies that the Rust types, generated views, and archive fixture remain synchronized. catalog-roundtrip writes a catalog through CatalogInput::encode() in MoonBit, then validates and inspects it with Rust rkyv::access.

#Benchmarks

Native release results for a 4,096-element Vec<u32>. The archive is built once outside each measured iteration. MoonBit numbers are benchmark means; Rust numbers are Criterion 95% confidence intervals, so treat this as an end-to-end comparison rather than a precise instruction-level comparison.

Environment: arm64, macOS 26.5.2, Moon 0.1.20260713 / moonc 0.10.4, Rust 1.96.0, and rkyv 0.8.17.

OperationMoonBit nativeRust rkyv native
Checked header + length validation6.33 ns3.01 ns
Validated lazy selected element6.84 ns1.44 ns
Checked root + selected lazy element9.81 ns3.32 ns
Checked eager materialization of all 4K elements478.40 ns256.22 ns
Copy from a validated view to a reused FixedArray / Rust Vec200.39 ns200.15 ns
Checked copy to a reused FixedArray / Rust Vec198.22 ns201.11 ns
Copy to a reused MutArrayView1.50 µs

The Rust comparison uses rkyv::access, its public checked API. Both sides receive the same already-encoded 4K Vec<u32> archive; archive construction is outside every timed loop. MoonBit uses --target native; target runtimes, allocation strategy, and compiler optimization are all included in these numbers. Re-run on the target system before making a deployment decision:

just bench just bench-profile just bench-compare just profile just host-js

For throughput-sensitive code, retain a U32VecView for repeated get calls, use FixedArray with copy_into for reused storage, and prefer to_fixed_array_fast() to to_array_fast() when a growable Array is not required. MutArrayView keeps a convenient subrange but is not the native bulk-copy path.

#Rust interoperability

just conformance verifies both directions byte-for-byte:

  • MoonBit reads bytes generated by Rust rkyv::to_bytes.
  • Rust rkyv::access accepts bytes generated by MoonBit encode_vec_u32 and encode_string.
  • The catalog example proves generated MoonBit-to-Rust archives for nested structs, primitive vectors, Vec<String>, Option<Vec<T>>, and fieldless enums.

Arbitrary #[derive(Archive)] structs, tuples, enums, generic collections, and general Vec<T> are outside this published compatibility guarantee.

#Experimental typed code generation

codegen/rust contains RkyvMbt, a Rust derive that asks the Rust compiler for the concrete Archived<T> size and field offsets, then renders a typed MoonBit view. It supports named structs with numeric primitives, bool, String, Vec<primitive>, Vec<String>, nested derive-enabled named structs, their vectors, and one-level Option<T> including Option<Vec<T>>. Fieldless enums generate a strict tag view and a type-safe writer through RkyvMbtEnum.

render_moonbit_with_encoder() additionally generates TypeInput::new and TypeInput::encode for every supported struct field. The generated writer uses the Rust compiler's exact field offsets and Archived<T> size rather than recomputing struct layout from field declaration order.

The Rust crates are experimental and are not published to crates.io. See codegen/rust/README.md for the supported profile and usage.

#Development

just fmt just check just test just conformance just codegen

#License

Apache-2.0. See LICENSE.

#
EnvelopeError

pub suberror EnvelopeError {
InvalidMagic
UnsupportedVersion(Byte)
UnsupportedFormat(Byte)
InvalidLength(Int)
PayloadLimitExceeded(actual~ : Int, limit~ : Int)
ChecksumMismatch(expected~ : UInt, actual~ : UInt)
} derive(Eq,
Debug
)

Envelope errors are intentionally separate from RkyvError: callers can reject a bad transport blob before attempting any schema-dependent reads.

#
RkyvError

pub suberror RkyvError {
OutOfBounds(offset~ : Int, size~ : Int, length~ : Int)
InvalidPointerWidth(Int)
InvalidAlignment(Int)
InvalidRelativePointer(Int)
LengthOverflow(Int)
InvalidCollectionLength(Int)
InvalidElementSize(Int)
DestinationTooSmall(required~ : Int, actual~ : Int)
InvalidUtf8
InvalidBool(Byte)
InvalidOptionTag(Byte)
InvalidUnionTag(Byte)
DepthLimit(Int)
} derive(Eq,
Debug
)

Errors returned while safely reading an archive. This reader deliberately checks bounds, unlike rkyv's access_unchecked APIs.

#
SchemaError

pub(all) suberror SchemaError {
TypeMismatch(expected~ : String)
FieldCountMismatch(expected~ : Int, actual~ : Int)
FieldMismatch(expected~ : String, actual~ : String)
} derive(Eq,
Debug
)

Errors raised by the host schema encoder before it emits an invalid archive.

#
ArchiveEnvelope

pub struct ArchiveEnvelope {
version : Int
format : Format
payload : Bytes
}

The self-identifying wrapper used when an archive crosses a trust boundary. The contained payload is still a standard rkyv byte sequence: the wrapper is optional and is never part of a Rust Archived<T> layout.

#
ArchiveEnvelope::archive_format

fn ArchiveEnvelope::archive_format(self : ArchiveEnvelope) -> Format
Returns the rkyv reader format declared by this envelope.

#
ArchiveEnvelope::payload_bytes

fn ArchiveEnvelope::payload_bytes(self : ArchiveEnvelope) -> Bytes
Returns the validated rkyv payload owned by this envelope.

#
Endian

pub enum Endian {
Little
Big
} derive(Eq,
Debug
)

Byte order used by an rkyv archive.

#
Field

pub(all) struct Field {
name : String
schema : Schema
}

A named field in a host-defined archived struct.

#
Format

pub struct Format {
endian : Endian
pointer_width : Int
aligned : Bool
} derive(Eq,
Debug
)

rkyv wire-format settings. The default is rkyv's standard aligned, little-endian, 32-bit-pointer format.

#
Format::big_endian

fn Format::big_endian(pointer_width : Int, aligned : Bool) -> Format

Creates a big-endian rkyv format.

#
Format::new

fn Format::new(endian : Endian, pointer_width : Int, aligned : Bool) -> Format

Creates a format explicitly. Use this when matching non-default rkyv feature flags in a Rust producer.

#
Format::pointer_bytes

fn Format::pointer_bytes(self : Format) -> Int raise RkyvError

Returns the size of archived pointers and usize values in bytes.

#
MutU32VecView

pub struct MutU32VecView {
bytes : MutArrayView[Byte]
data_offset : Int
length : Int
}

A mutable view of an existing ArchivedVec<u32>. It can replace elements in place, but cannot change the vector length or move its data.

#
MutU32VecView::length

fn MutU32VecView::length(self : MutU32VecView) -> Int

Returns the fixed archived element count of this mutable vector view.

#
MutU32VecView::set

fn MutU32VecView::set(self : MutU32VecView, index : Int, value : UInt) -> Bool

Replaces one existing vector element in place. Returns false for an out-of-range index; this API never resizes or relocates the vector.

#
MutView

pub struct MutView {
schema : Schema
bytes : MutArrayView[Byte]
offset : Int
}

A schema-directed mutable view over a caller-owned archive buffer. It can update only fixed-width fields, so no relative pointer or collection length can become invalid.

#
MutView::field

fn MutView::field(self : MutView, name : String) -> MutView?

Returns a nested mutable struct field by name. None means this view is not a struct or that the requested field does not exist.

#
MutView::set_bool

fn MutView::set_bool(self : MutView, value : Bool) -> Bool

Replaces a boolean field in place. Returns false if this view's schema is not Schema::Bool.

#
MutView::set_string

fn MutView::set_string(self : MutView, value : String) -> Bool raise RkyvError

Replaces a string only when its UTF-8 byte length is unchanged. The archive representation, relative pointer, and inline/out-of-line choice therefore remain valid. Returns false for a different-length string or a non-string schema; malformed archived strings raise RkyvError before any write.

#
MutView::set_u32

fn MutView::set_u32(self : MutView, value : UInt) -> Bool

Replaces a u32 field in place. Returns false if this view's schema is not Schema::U32.

#
MutView::vec_u32_mut

fn MutView::vec_u32_mut(self : MutView) -> MutU32VecView? raise RkyvError

Opens a mutable view of an existing Vec<u32>. The header and full element span are checked before returning the view. The snapshot used for validation is discarded; writes always target the caller-owned mutable buffer.

#
Reader

pub struct Reader {
bytes : Bytes
format : Format
}

A view over an rkyv archive. It keeps the caller's Bytes buffer and never copies data while resolving primitive values or relative pointers.

#
Reader::bytes

fn Reader::bytes(self : Reader) -> Bytes

Returns the archive's backing bytes without copying them.

#
Reader::new

fn Reader::new(bytes : Bytes) -> Reader

Creates a reader for rkyv's default format.

#
Reader::read_bool

fn Reader::read_bool(self : Reader, offset : Int) -> Bool raise RkyvError

Reads rkyv's one-byte boolean representation.

#
Reader::read_bool_strict

fn Reader::read_bool_strict(self : Reader, offset : Int) -> Bool raise RkyvError

Reads a boolean for an untrusted archive. Unlike read_bool, this rejects values other than rkyv's canonical 0 and 1 discriminants.

#
Reader::read_bytes_view

fn Reader::read_bytes_view(self : Reader, offset : Int, length : Int) -> BytesView raise RkyvError

Returns a borrowed, bounds-checked range of the archive without copying. This is intended for archived byte blobs and integrations that can consume raw UTF-8 bytes directly. It does not validate a higher-level rkyv layout or UTF-8; use read_string when a decoded, validated String is needed.

#
Reader::read_f32

fn Reader::read_f32(self : Reader, offset : Int) -> Float raise RkyvError

Reads an IEEE 754 single-precision float without changing its bit pattern.

#
Reader::read_f64

fn Reader::read_f64(self : Reader, offset : Int) -> Double raise RkyvError

Reads an IEEE 754 double-precision float without changing its bit pattern.

#
Reader::read_i16

fn Reader::read_i16(self : Reader, offset : Int) -> Int raise RkyvError

Reads a signed 16-bit integer.

#
Reader::read_i32

fn Reader::read_i32(self : Reader, offset : Int) -> Int raise RkyvError

Reads a signed 32-bit integer.

#
Reader::read_i64

fn Reader::read_i64(self : Reader, offset : Int) -> Int64 raise RkyvError

Reads a signed 64-bit integer.

#
Reader::read_i8

fn Reader::read_i8(self : Reader, offset : Int) -> Int raise RkyvError

Reads a signed 8-bit integer.

#
Reader::read_option_value_offset

fn Reader::read_option_value_offset(self : Reader, offset : Int, value_alignment : Int) -> Int? raise RkyvError

Resolves an ArchivedOption<T> tag into the offset of its present value. The caller supplies the archived alignment of T; this keeps type-specific layout in generated bindings while centralizing rkyv's tag/padding rule.

#
Reader::read_option_value_offset_strict

fn Reader::read_option_value_offset_strict(self : Reader, offset : Int, value_alignment : Int) -> Int? raise RkyvError

Resolves an ArchivedOption<T> for an untrusted archive and rejects tags other than rkyv's canonical 0 (None) and 1 (Some) values.

#
Reader::read_rel_ptr

fn Reader::read_rel_ptr(self : Reader, offset : Int) -> Int raise RkyvError

Resolves an ordinary rkyv relative pointer. ArchivedString is the one exception: its pointer is relative to the string representation's start.

#
Reader::read_rel_ptr_offset

fn Reader::read_rel_ptr_offset(self : Reader, offset : Int) -> Int raise RkyvError

Reads the signed raw offset stored in an rkyv relative pointer.

#
Reader::read_string

fn Reader::read_string(self : Reader, offset : Int) -> String raise RkyvError

Reads rkyv 0.8's hybrid inline/out-of-line ArchivedString layout.

#
Reader::read_u16

fn Reader::read_u16(self : Reader, offset : Int) -> UInt raise RkyvError

Reads an unsigned 16-bit integer using the archive's configured endianness.

#
Reader::read_u32

fn Reader::read_u32(self : Reader, offset : Int) -> UInt raise RkyvError

Reads an unsigned 32-bit integer using the archive's configured endianness.

#
Reader::read_u64

fn Reader::read_u64(self : Reader, offset : Int) -> UInt64 raise RkyvError

Reads an unsigned 64-bit integer using the archive's configured endianness.

#
Reader::read_u8

fn Reader::read_u8(self : Reader, offset : Int) -> Byte raise RkyvError

Reads an unsigned 8-bit integer.

#
Reader::read_union_tag

fn Reader::read_union_tag(self : Reader, offset : Int, accepted : Array[Byte]) -> Byte raise RkyvError

Reads a #[repr(u8)] enum tag for an untrusted archive. The accepted tags are supplied by generated bindings; an unknown value is raised from inside this package because RkyvError is intentionally a read-only suberror to downstream MoonBit packages.

#
Reader::read_usize

fn Reader::read_usize(self : Reader, offset : Int) -> Int raise RkyvError

Reads rkyv's fixed-width archived usize. Offsets larger than MoonBit's addressable Int range are rejected instead of wrapping.

#
Reader::read_vec_header

fn Reader::read_vec_header(self : Reader, offset : Int) -> VecHeader raise RkyvError

Reads the header of an ArchivedVec<T>. Element decoding remains a schema concern and belongs in generated or hand-written bindings.

#
Reader::read_vec_header_with_element_size

fn Reader::read_vec_header_with_element_size(self : Reader, offset : Int, element_size : Int) -> VecHeader raise RkyvError

Reads an ArchivedVec<T> header and validates its complete element range. Generated bindings supply the archived element size measured by the Rust compiler, preventing a corrupted length from escaping as a lazy view.

#
Reader::read_vec_u32

fn Reader::read_vec_u32(self : Reader, offset : Int) -> U32VecView raise RkyvError

Reads an ArchivedVec<u32> as a zero-copy typed view. The whole byte span is validated before returning so subsequent indexing is bounded by the archive rather than a potentially corrupted archived length.

#
Reader::read_vec_u32_into

fn Reader::read_vec_u32_into(self : Reader, offset : Int, destination : FixedArray[UInt]) -> Int raise RkyvError

Validates an ArchivedVec<u32> and copies it into caller-owned fixed-size storage. This convenience API does not expose or retain a view; a short destination is left untouched and returns DestinationTooSmall. On success, it returns the copied element count for making an ArrayView.

#
Reader::read_vec_u32_length

fn Reader::read_vec_u32_length(self : Reader, offset : Int) -> Int raise RkyvError

Validates an ArchivedVec<u32> and returns only its element count. Unlike read_vec_u32, this does not create a U32VecView or retain the archive. Use it for header-only consumers; use read_vec_u32 when reading elements.

#
Reader::root_offset

fn Reader::root_offset(self : Reader, size : Int) -> Int raise RkyvError

Gets the byte offset of a root whose archived representation has size bytes. rkyv writes dependencies first and puts the root at the end.

#
Reader::validate_range

fn Reader::validate_range(self : Reader, offset : Int, size : Int) -> Unit raise RkyvError

Validates that a fixed-size archived value is wholly contained in the input. Generated nested views use this before retaining an inline offset.

#
Reader::with_format

fn Reader::with_format(bytes : Bytes, format : Format) -> Reader

Creates a reader with an explicitly selected rkyv format.

#
Schema

pub(all) enum Schema {
U8
U16
U32
U64
I8
I16
I32
I64
F32
F64
Bool
String
VecU32
Vec(Schema)
Option(Schema)
Struct(Array[Field])
StructLayout(Array[Field], Array[Int], Int)
TaggedUnion(Array[Variant], Int, Int, Int)
}

A MoonBit-owned schema for the rkyv 0.8 default archive profile. It is a small, explicit host-side alternative to the Rust layout derive. Struct uses field order as its contract; generated Rust writers use StructLayout with compiler-provided offsets and archived size instead.

#
Schema::encode

fn Schema::encode(self : Schema, value : Value) -> Bytes raise SchemaError

Encodes a value into an immutable rkyv archive. Use encode_mut when the caller intends to apply later fixed-width in-place updates.

#
Schema::encode_mut

fn Schema::encode_mut(self : Schema, value : Value) -> Array[Byte] raise SchemaError

Encodes a host-defined value into a caller-owned mutable rkyv archive. This function is target-independent, so moon build --target js produces the same bytes without Rust.

#
Schema::root

fn Schema::root(self : Schema, bytes : Bytes) -> View raise RkyvError

Opens the root representation described by this host schema. The complete inline span is validated immediately; out-of-line values are validated by their respective reader accessors.

#
Schema::root_mut

fn Schema::root_mut(self : Schema, bytes : MutArrayView[Byte]) -> MutView raise RkyvError

Opens a mutable root over caller-owned archive bytes. The inline root span is validated before the view is returned; collection fields are additionally validated when vec_u32_mut is requested.

#
Schema::validate

fn Schema::validate(self : Schema, bytes : Bytes) -> Unit raise RkyvError

Fully validates a host-defined archive before it is exposed to an application. This checks every reachable relative pointer, string, vector extent, option tag, boolean discriminant, and tagged-union discriminant.

#
U32VecView

pub struct U32VecView {
bytes : Bytes
format : Format
data_offset : Int
length : Int
}

A zero-copy typed view of an ArchivedVec<u32>. Generated bindings can retain this view and resolve only the elements requested by their caller.

#
U32VecView::at

fn U32VecView::at(self : U32VecView, index : Int) -> UInt?

Resolves one element without copying the rest of the archived vector. An index outside the vector's logical range returns None.

#
U32VecView::copy_into

fn U32VecView::copy_into(self : U32VecView, destination : FixedArray[UInt]) -> Unit raise RkyvError

Copies every element into caller-owned fixed-size storage. The view has already validated the archived span. A destination that is too short is left untouched so callers can safely reuse it after an error.

#
U32VecView::copy_into_mut_view

fn U32VecView::copy_into_mut_view(self : U32VecView, destination : MutArrayView[UInt]) -> Unit raise RkyvError

Copies every element into a caller-owned mutable array window. The window can start at an arbitrary offset in either an Array or FixedArray; it uses v128 loads and scalar stores rather than the native FixedArray bulk-copy FFI.

#
U32VecView::data_byte_offset

fn U32VecView::data_byte_offset(self : U32VecView) -> Int

Returns the byte offset of the first u32 element within the archive. Integrations that store an entire archive in 32-bit shared memory can convert this to a word offset after checking that it is divisible by four.

#
U32VecView::get

fn U32VecView::get(self : U32VecView, index : Int) -> UInt?

Resolves an element from a fully validated archived vector. read_vec_u32 verifies the complete element span before constructing this view, so a valid in-range index cannot fail byte-range validation. Out-of-range indices return None.

#
U32VecView::length

fn U32VecView::length(self : U32VecView) -> Int

Returns the number of elements in the archived vector without decoding it.

#
U32VecView::to_array

fn U32VecView::to_array(self : U32VecView) -> Array[UInt]

Decodes every element into a MoonBit array. Keep the U32VecView when a caller only needs selected elements; use this at API boundaries that need an owned collection.

#
U32VecView::to_array_fast

fn U32VecView::to_array_fast(self : U32VecView) -> Array[UInt]

Decodes every element after read_vec_u32 has validated the full element span. Unlike the compatibility wrapper, this cannot fail.

#
U32VecView::to_fixed_array_fast

fn U32VecView::to_fixed_array_fast(self : U32VecView) -> FixedArray[UInt]

Decodes every element after validation into a fixed-size array. This avoids the extra allocation and copy required to return a growable Array.

#
Value

pub(all) enum Value {
U8(Byte)
U16(UInt)
U32(UInt)
U64(UInt64)
I8(Int)
I16(Int)
I32(Int)
I64(Int64)
F32(Float)
F64(Double)
Bool(Bool)
String(String)
VecU32(Array[UInt])
Vec(Array[Value])
Option(Value?)
Struct(Array[ValueField])
Tagged(Byte, Value?)
}

A dynamic value accepted by a Schema encoder.

#
ValueField

pub(all) struct ValueField {
name : String
value : Value
}

A named field value. Names make accidental schema/value ordering mistakes fail before any root archive object is written.

#
Variant

pub(all) struct Variant {
name : String
tag : Byte
schema : Schema
}

One variant in a host-defined #[repr(u8)] archived enum. tag_offset, payload_offset, and archived_size are supplied by TaggedUnion, so the union's native Rust layout is always an explicit part of the contract.

#
VecHeader

pub struct VecHeader {
data_offset : Int
length : Int
} derive(Eq,
Debug
)

The two fields that make up an ArchivedVec<T> header.

#
View

pub struct View {
schema : Schema
reader : Reader
offset : Int
}

A schema-directed, zero-copy view over a host-defined archived value. Field lookup stays dynamic because the schema is a MoonBit value rather than source generated from Rust's compiler layout.

#
View::field

fn View::field(self : View, name : String) -> View?

Returns a nested struct field by name. None means this view is not a struct or that the requested field does not exist in its host schema.

#
View::option_value

fn View::option_value(self : View) -> View? raise RkyvError

Opens the present value of a host-defined Option<T>. None means either this view is not optional or the archived value is absent.

#
View::read_bool

fn View::read_bool(self : View) -> Bool? raise RkyvError

Reads a boolean when this view's schema is Schema::Bool.

#
View::read_f32

fn View::read_f32(self : View) -> Float? raise RkyvError

Reads an f32 when this view's schema is Schema::F32.

#
View::read_f64

fn View::read_f64(self : View) -> Double? raise RkyvError

Reads an f64 when this view's schema is Schema::F64.

#
View::read_i16

fn View::read_i16(self : View) -> Int? raise RkyvError

Reads an i16 when this view's schema is Schema::I16.

#
View::read_i32

fn View::read_i32(self : View) -> Int? raise RkyvError

Reads an i32 when this view's schema is Schema::I32.

#
View::read_i64

fn View::read_i64(self : View) -> Int64? raise RkyvError

Reads an i64 when this view's schema is Schema::I64.

#
View::read_i8

fn View::read_i8(self : View) -> Int? raise RkyvError

Reads an i8 when this view's schema is Schema::I8.

#
View::read_string

fn View::read_string(self : View) -> String? raise RkyvError

Reads a string when this view's schema is Schema::String.

#
View::read_tag

fn View::read_tag(self : View) -> Byte? raise RkyvError

Reads an explicit tagged-union discriminant without resolving its payload.

#
View::read_u16

fn View::read_u16(self : View) -> UInt? raise RkyvError

Reads a u16 when this view's schema is Schema::U16.

#
View::read_u32

fn View::read_u32(self : View) -> UInt? raise RkyvError

Reads a u32 when this view's schema is Schema::U32.

#
View::read_u64

fn View::read_u64(self : View) -> UInt64? raise RkyvError

Reads a u64 when this view's schema is Schema::U64.

#
View::read_u8

fn View::read_u8(self : View) -> Byte? raise RkyvError

Reads a u8 when this view's schema is Schema::U8.

#
View::read_vec_i16

fn View::read_vec_i16(self : View) -> Array[Int]? raise RkyvError

Materializes a generic ArchivedVec<i16> after validating its complete span. The dedicated lazy U32VecView remains available for the hot u32 path; other primitive vectors favor a compact general implementation.

#
View::read_vec_u32

fn View::read_vec_u32(self : View) -> U32VecView? raise RkyvError

Opens a zero-copy ArchivedVec<u32> view when this schema declares one.

#
View::read_vec_u64

fn View::read_vec_u64(self : View) -> Array[UInt64]? raise RkyvError

Materializes a generic ArchivedVec<u64> after validating its complete span. It is useful for host-side inspection and test conformance; generated bindings use lazy per-field views for all primitive vectors.

#
View::tagged_value

fn View::tagged_value(self : View) -> View? raise RkyvError

Opens the active explicit tagged-union payload. Unknown tags are rejected rather than treated as absent, which keeps untrusted validation strict.

#
crc32

fn crc32(bytes : Bytes) -> UInt

Computes the IEEE CRC-32 used by the envelope. This small table-free form keeps the JavaScript runtime dependency-free and has negligible cost for the small headers and metadata archives this package targets.

#
decode_envelope

fn decode_envelope(bytes : Bytes) -> ArchiveEnvelope raise EnvelopeError

Parses and checks a v1 envelope before returning a standalone rkyv payload. Applications should pass decoded.archive_format() to Reader::with_format when the producer may select non-default rkyv format features.

#
decode_envelope_with_limit

fn decode_envelope_with_limit(bytes : Bytes, max_payload_length : Int) -> ArchiveEnvelope raise EnvelopeError

Parses an envelope while enforcing a caller-provided payload ceiling before copying any payload bytes. Use this at network and storage boundaries to keep malformed length headers from causing unbounded allocation.

#
default_format

fn default_format() -> Format

Returns the format used by rkyv when no format feature is enabled.

#
encode_envelope

fn encode_envelope(payload : Bytes) -> Bytes

Wraps a default-format rkyv payload in the versioned envelope. Use encode_envelope_with_format when Rust was built with a non-default rkyv format feature.

#
encode_envelope_with_format

fn encode_envelope_with_format(payload : Bytes, format : Format) -> Bytes raise EnvelopeError

Wraps an rkyv payload with its exact reader format. The payload is copied once so the returned value is a standalone transport buffer.

#
encode_string

fn encode_string(value : String) -> Bytes

Encodes a root String in rkyv 0.8's default format. Strings up to eight UTF-8 bytes are inline; longer strings are written before their root repr.

#
encode_vec_u32

fn encode_vec_u32(values : Array[UInt]) -> Bytes

Encodes a root Vec<u32> in rkyv's default format. The element data is emitted before the ArchivedVec root header, as required by rkyv.

#
envelope_header_size

let envelope_header_size : Int

Number of bytes before the rkyv payload starts.

#
envelope_version

let envelope_version : Byte

The current envelope protocol version.

#
require_validation_depth

fn require_validation_depth(remaining_depth : Int) -> Unit raise RkyvError

Enforces the recursion limit used by generated full-archive validators. The constructor stays inside this package because RkyvError is a suberror and cannot be constructed by generated client packages.