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
| Value | Message | Reading it |
|---|---|---|
1 | 01 ff 04 01 00 00 00 | head 01, NOT_NULL ff, INT32 04, four bytes little-endian |
"ab" | 01 ff 15 0a 61 62 | STRING 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
| Bytes | What they are |
|---|---|
01 | the header, cross-language flag set |
00 | a value that may be pointed at: the root, which is reference zero |
1b ac02 | STRUCT (27), user type id 300 |
eecb234f | the fingerprint of the schema |
02 | val, as a zig-zag varint |
fe 00 | next: 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)