moon/fory Apache Fory · MoonBit

Examples

The bytes, and what they mean.

Each example below is what moon run src/examples/tour prints. The hex on the right is the whole message, header and all.

Values and containers

let n = @moonfory.dumps(1)
let s = @moonfory.dumps("ab")
let xs = @moonfory.dumps([1, 2, 3])
let m = @moonfory.dumps({ "a": 1 })

println(@base16.encode(n))   // 01ff0401000000
println(@base16.encode(s))   // 01ff150a6162
println(@base16.encode(xs))  // 01ff16030804010000000200000003000000
println(@base16.encode(m))   // 01ff180100011504066101000000
ValueMessageReading it
101 ff 04 01 00 00 00head 01, NOT_NULL ff, INT32 04, four bytes little-endian
"ab"01 ff 15 0a 61 62STRING 15, header 0a = (2 << 2) | UTF-8, then the bytes
[1, 2, 3]01 ff 16 03 08 04 …LIST 16, count 3, element header 08 (same type), element type INT32
{"a": 1}01 ff 18 01 00 01 15 04 …MAP 18, total 1, chunk header, chunk size, key and value types

A struct, described once

A struct's field order is not the order you write it in. Fory sorts: non-nullable primitives first, then nullable primitives, then everything else, and inside each group by width then by type ID then by name. The writer and the reader compute the same order from the same StructMeta, so neither side has to transmit it.

let meta = @moonfory.StructMeta::new(
  "Point",
  1,
  [
    @moonfory.FieldMeta::new("x", @moonfory.TypeId::Int32),
    @moonfory.FieldMeta::new("y", @moonfory.TypeId::Int32),
  ],
)
let point = @moonfory.Value::Struct(meta, [
  ("x", @moonfory.Value::Int(1L)),
  ("y", @moonfory.Value::Int(2L)),
])
println(@base16.encode(@moonfory.dumps(point)))
// 01ff1b0194d631870100000002000000

1b is STRUCT (27), 01 is the user type id, 94d63187 is the schema's fingerprint, and then the two INT32 fields in Fory order. Read it back with @moonfory.loads(bytes, schemas=[meta]).

A row, read by field

The row format exists for the case the binary format handles badly: a reader that wants one field of a wide record. The bitmap and the fixed slots let it jump straight to the field it needs.

let schema = @moonfory.RowSchema::new([
  { name: "id",   ty: @moonfory.RowType::Int64,  nullable: false },
  { name: "name", ty: @moonfory.RowType::String, nullable: false },
  { name: "age",  ty: @moonfory.RowType::Int32,  nullable: true  },
])
let row = @moonfory.Row::new(schema)
row.set(0, @moonfory.RowValue::Int64(7L))
row.set(1, @moonfory.RowValue::String("ada"))
row.set(2, @moonfory.RowValue::Null)

let cur = @moonfory.RowCursor::new(row.finish()[:], schema)
println(cur.get_field(1))  // String("ada")
println(cur.is_null(2))   // true

One body, two references

Reference tracking costs a byte per tracked value, so it is off by default. Turn it on and mark the shared value with a key.

let once  = @moonfory.shared(7, "same")
let twice = [once, once]
let bytes = @moonfory.dumps(twice, track_ref=true)
let back : Array[@moonfory.Shared[String]] =
  @moonfory.loads(bytes, track_ref=true)
println(back[0].value == back[1].value)  // true

A struct that contains itself

Identity is a key you choose, because a MoonBit value carries none: Shared writes the value under a key, Pointer points at one already written. A cycle is the two together, and these are the bytes Go's fory writes for the same schema.

let node = @moonfory.StructMeta::new("Node", 300, [
  @moonfory.FieldMeta::new("val", @moonfory.TypeId::VarInt32),
  @moonfory.FieldMeta::new("next", @moonfory.TypeId::Struct, track_ref=true),
])
let value = @moonfory.Value::Struct(node, [
  ("val", @moonfory.Value::Int(1L)),
  ("next", @moonfory.Value::Pointer(0)),  // the root is reference zero
])
println(@base16.encode(@moonfory.dumps(value, track_ref=true)))
// 01001bac02eecb234f02fe00
BytesWhat they are
01the header, cross-language flag set
00a value that may be pointed at: the root, which is reference zero
1b ac02STRUCT (27), user type id 300
eecb234fthe fingerprint of the schema
02val, as a zig-zag varint
fe 00next: a reference to reference zero — itself

When it refuses

A malformed message is a Refused, with the offset that produced it. This is the shape of every failure:

let outcome = try {
  let _ : @moonfory.Value = @moonfory.loads(@base16.decode("01ff040100"))
  "read"
} catch {
  failure => failure.to_string()
} noraise {
  _ => "read"
}
// Truncated(at=3, want=4, got=3)

Read the interface