std/encoding

Encoding and decoding: what other languages need reflection for (serialization, config mapping, database rows, schemas, --help texts).

const json = Json()
const text = json.encode user
const back = json.decode<User>(text)?

A value is its constructor call. The compiler knows every type's constructor and offers it in three forms: written (Encode), read (Decode) and described without a value (Describe). A format (Json, a database driver, a command line parser) implements Encoder, Decoder or Describer and never sees a type. There is no tree in between: a value is written while the type describes itself, and nothing is lost on the way.

All three forms are generated for a type whose constructor is usable from outside, over exactly the constructor's parameters: a private field with a default is not one of them, so a cache stays out without an annotation. What the compiler generates is ordinary code over the vocabulary, and could be written by hand:

extend User with Encode {
  fn encode<Target: Encoder>(var target: Target) {
    target.record "app/User"
    target.field "name"
    name.encode target
    target.field "tags"
    tags.encode target
    target.finish()
  }
}

The three traits take their target as a bound, not as a trait type: a format hands its own encoder over as a var path and reads the result out of it afterwards, and the derived code is a direct call chain with no table. EncodedValue is what a program holds where it has left the type behind, and Structure is a type's description as a value. docs/design/ENCODING.md is the design.

This package also has the plainer kind of encoding: bytes as text and back. Base64Alphabet, Base32Alphabet and Bytes.hexEncoded/String.hexDecoded/String.base64Decoded are RFC 4648, unrelated to Encode/Decode above - a value's own Encoder.bytes writes raw bytes, and a format reaches for one of these only where the wire itself is text (a JWT, a !!binary YAML scalar, a TOTP secret).

Modules

Everything