A Rust library for parsing, evaluating, and manipulating UZON — a typed configuration language.
use uzon::{from_str, Value};
let bindings = from_str(r#"
name is "Alice"
age is 30
server is { host is "localhost", port is 8080 }
"#)?;
assert_eq!(bindings["name"].as_str(), Some("Alice"));
assert_eq!(bindings["age"].as_i64(), Some(30));Add to your Cargo.toml:
[dependencies]
uzon = "0.10"use uzon::{from_str, from_str_plain, to_string, uzon, Value};
// Parse UZON text
let bindings = from_str(r#"
name is "Alice"
scores are 95, 88, 92
config is { debug is true, port is 8080 }
"#).unwrap();
// Access values
let name = &bindings["name"];
assert_eq!(name.as_str(), Some("Alice"));
// Build values programmatically
let value = uzon!({
"name": "Bob",
"age": 25,
"tags": ["rust", "uzon"]
});
assert_eq!(value["name"], "Bob");
assert_eq!(value["age"], 25);
// Arithmetic
let result = Value::int(10) + Value::int(20);
assert_eq!(result, 30);
// Deserialize into Rust types
#[derive(serde::Deserialize)]
struct Config { debug: bool, port: u16 }
let config: Config = uzon::from_str_as(r#"
debug is true
port is 8080
"#).unwrap();UZON is a declarative data expression format. Bindings use is instead of =, types use as, and evaluation order is determined by the dependency graph — forward references are allowed.
// Primitives
name is "Alice"
age is 30
score is 95.5
active is true
nothing is null
// Typed numbers
byte is 255 as u8
precise is 3.14 as f32
// Collections
tags are "web", "api" // are = list sugar
ids are 1, 2, 3 as [i32]
point is (10, 20) // tuple
server is { host is "localhost", port is 8080 }
// Expressions and string interpolation
total is age + 1
greeting is "Hello, {name}!"
grade is if score >= 90.0 then "A" else "B"
// Case expressions — three modes
label is case grade
when "A" then "excellent"
when "B" then "good"
else "ok"
// Type dispatch (untagged unions)
u is 42 as i32 from union i32, string
type_label is case type u
when i32 then "integer"
when string then "text"
else "other"
// Variant dispatch (tagged unions)
status is "ok" named success
from success as string, error as string
called Result
status_label is case named status
when success then "good"
when error then "bad"
else "unknown"
// Enums
_color is red from red, green, blue called Color
selected is green as Color
// Type checking
is_int is u is type i32 // true
is_str is u is not type string // false
// Functions
add is function a as i32, b as i32 returns i32 { a + b }
sum is add(3, 4)
// Standard library
doubled is std.map(ids, function n as i32 returns i32 { n * 2 })
count is std.len(tags)
// Struct operations
modified is server with { port is 443 }
extended is server plus { tls is true, cert is "/path" }
// Environment variables
port is env.PORT to u16 or else 8080
// Undefined coalescing
fallback is server.missing or else "default"
// Field extraction
host is of server // = server.host
Three entry points for parsing UZON text:
use std::collections::BTreeMap;
use std::path::Path;
use uzon::{from_str, from_str_plain, from_path, Value, Result};
// Parse and evaluate, preserving UZON type wrappers (enums, unions, etc.)
fn from_str(source: &str) -> Result<BTreeMap<String, Value>>
// Parse and evaluate, stripping UZON wrappers to plain Rust-friendly types
fn from_str_plain(source: &str) -> Result<BTreeMap<String, Value>>
// Parse and evaluate from a file path
fn from_path(path: &Path) -> Result<BTreeMap<String, Value>>from_str vs from_str_plain: from_str preserves UZON-specific types (enums carry variant sets, tuples are distinct from lists). from_str_plain calls .to_plain() on every value, converting enums to strings, tuples to lists, and unwrapping unions.
let source = r#"
_color is red from red, green, blue called Color
choice is red as Color
pair is (1, 2)
"#;
// from_str: choice is Value::Enum, pair is Value::Tuple
let rich = from_str(source).unwrap();
// from_str_plain: choice is Value::String, pair is Value::List
let plain = from_str_plain(source).unwrap();For custom configuration, use Evaluator directly:
use uzon::{Evaluator, EvalOptions};
let options = EvalOptions {
filename: Some("config.uzon".into()), // for error messages
env: None, // use std::env by default
plain: false, // preserve UZON types
};
let mut evaluator = Evaluator::new(options);
// evaluator.evaluate(&document) — requires a parsed Document (internal API)| Field | Type | Default | Description |
|---|---|---|---|
filename |
Option<PathBuf> |
None |
Source filename for error reporting |
env |
Option<HashMap<String, String>> |
None |
Environment variables (defaults to std::env::vars()) |
plain |
bool |
false |
If true, auto-calls to_plain() on results |
Value is the core runtime representation:
pub enum Value {
Null, // null
Undefined, // "does not exist"
Bool(bool), // true / false
Integer(UzonInteger), // i128 + type annotation (i8..i128, u8..u128)
BigInteger(num_bigint::BigInt), // arbitrary precision
Float(UzonFloat), // f64 + type annotation (f16..f128)
String(String), // UTF-8 string
List(UzonList), // [a, b, c] — homogeneous sequence
Tuple(UzonTuple), // (a, b, c) — fixed-length heterogeneous
Struct(IndexMap<String, Value>), // { key is value } — ordered map
Enum(UzonEnum), // variant from a set
Union(UzonUnion), // untagged union
TaggedUnion(UzonTaggedUnion), // tagged union
Function(UzonFunction), // closure
}pub struct UzonInteger {
pub value: i128,
pub type_ann: IntegerType, // i64 by default
pub explicit: bool, // true if annotation was in source (e.g., "42 as u8")
}| Method | Signature | Description |
|---|---|---|
new |
fn new(value: i128) -> Self |
Create with default type (i64) |
with_type |
fn with_type(value: i128, type_ann: IntegerType) -> Self |
Create with explicit type |
validate_range |
fn validate_range(&self) -> Result<(), String> |
Check value fits in annotated range |
checked_add |
fn checked_add(&self, other: &Self) -> Result<Self, String> |
Add with overflow check |
checked_sub |
fn checked_sub(&self, other: &Self) -> Result<Self, String> |
Subtract with overflow check |
checked_mul |
fn checked_mul(&self, other: &Self) -> Result<Self, String> |
Multiply with overflow check |
checked_div |
fn checked_div(&self, other: &Self) -> Result<Self, String> |
Divide (errors on zero) |
checked_rem |
fn checked_rem(&self, other: &Self) -> Result<Self, String> |
Remainder (errors on zero) |
checked_pow |
fn checked_pow(&self, exp: &Self) -> Result<Self, String> |
Power with overflow check |
checked_neg |
fn checked_neg(&self) -> Result<Self, String> |
Negate with overflow check |
pub enum IntegerType {
Arbitrary, // unbounded (maps to BigInt if needed)
I(u16), // signed: I(8)=i8, I(16)=i16, ..., I(128)=i128
U(u16), // unsigned: U(8)=u8, U(16)=u16, ..., U(128)=u128
}| Method | Signature | Description |
|---|---|---|
DEFAULT |
const IntegerType::I(64) |
Default type for integer literals |
is_default |
fn is_default(&self) -> bool |
Check if this is the default type |
range |
fn range(&self) -> Option<(i128, i128)> |
(min, max) inclusive; None for Arbitrary |
from_type_name |
fn from_type_name(name: &str) -> Option<IntegerType> |
Parse "i32", "u64", etc. |
display_name |
fn display_name(&self) -> String |
"i32", "u64", "integer", etc. |
pub struct UzonFloat {
pub value: f64,
pub type_ann: FloatType, // f64 by default
pub explicit: bool,
}| Method | Signature | Description |
|---|---|---|
new |
fn new(value: f64) -> Self |
Create with default type (f64) |
with_type |
fn with_type(value: f64, type_ann: FloatType) -> Self |
Create with explicit type |
add, sub, mul, div, rem |
fn op(&self, other: &Self) -> Result<Self, String> |
Arithmetic operations |
powf |
fn powf(&self, other: &Self) -> Result<Self, String> |
Power |
neg |
fn neg(&self) -> Self |
Negate |
pub enum FloatType { F16, F32, F64, F80, F128 }| Method | Signature | Description |
|---|---|---|
DEFAULT |
const FloatType::F64 |
Default type for float literals |
is_default |
fn is_default(&self) -> bool |
Check if this is the default type |
from_type_name |
fn from_type_name(name: &str) -> Option<FloatType> |
Parse "f32", "f64", etc. |
display_name |
fn display_name(&self) -> &'static str |
"f32", "f64", etc. |
pub struct UzonList {
pub elements: Vec<Value>,
pub element_type: Option<String>, // from `as [Type]` annotations
}| Method | Signature | Description |
|---|---|---|
new |
fn new(elements: Vec<Value>) -> Self |
Create without type annotation |
with_type |
fn with_type(elements: Vec<Value>, element_type: impl Into<String>) -> Self |
Create with element type |
len |
fn len(&self) -> usize |
Element count |
is_empty |
fn is_empty(&self) -> bool |
Check if empty |
UzonList also implements Deref<Target=Vec<Value>>, DerefMut, IntoIterator, and FromIterator<Value>.
pub struct UzonTuple {
pub elements: Vec<Value>,
}| Method | Signature | Description |
|---|---|---|
new |
fn new(elements: Vec<Value>) -> Self |
Create tuple |
len |
fn len(&self) -> usize |
Element count |
is_empty |
fn is_empty(&self) -> bool |
Check if empty |
pub struct UzonEnum {
pub value: String, // selected variant
pub variants: Vec<String>, // all possible variants
pub type_name: Option<String>, // type name if defined
}| Method | Signature | Description |
|---|---|---|
new |
fn new(value: impl Into<String>, variants: Vec<String>, type_name: Option<String>) -> Self |
Create enum |
pub struct UzonUnion {
pub value: Box<Value>,
pub types: Vec<String>, // possible type names
pub type_name: Option<String>,
}| Method | Signature | Description |
|---|---|---|
new |
fn new(value: Value, types: Vec<String>, type_name: Option<String>) -> Self |
Create union |
pub struct UzonTaggedUnion {
pub value: Box<Value>,
pub tag: String, // selected variant tag
pub variants: BTreeMap<String, Option<String>>, // tag -> type mapping
pub type_name: Option<String>,
}| Method | Signature | Description |
|---|---|---|
new |
fn new(value: Value, tag: impl Into<String>, variants: BTreeMap<String, Option<String>>, type_name: Option<String>) -> Self |
Create tagged union |
pub struct UzonFunction {
pub params: Vec<FunctionParam>,
pub return_type: TypeExpr,
pub body_bindings: Vec<Binding>,
pub body_expr: Node,
pub captured_bindings: BTreeMap<String, Value>,
pub captured_types: BTreeMap<String, TypeDef>,
pub type_name: Option<String>,
}Functions are opaque closures — they cannot be compared (PartialEq always returns false).
pub struct UzonUndefined; // sentinel type, Display prints "undefined"Value::int(42) // Integer with default i64 type
Value::float(3.14) // Float with default f64 type
Value::list(vec![Value::int(1)]) // List from Vecvalue.type_name() // -> "null", "bool", "integer", "float", "string",
// "list", "tuple", "struct", "enum", "union",
// "tagged union", "function"
value.is_null() // -> true for Value::Null
value.is_undefined() // -> true for Value::UndefinedStrip UZON-specific wrappers for simpler Rust consumption:
let plain = value.to_plain();
// Enum -> String
// Tuple -> List
// Union -> inner value
// TaggedUnion -> inner value
// Struct fields and List elements are recursively simplifiedType-safe extraction methods. All return Option — None if the type doesn't match.
value.as_bool() // -> Option<bool>
value.as_i64() // -> Option<i64> (also handles BigInteger)
value.as_i128() // -> Option<i128> (also handles BigInteger)
value.as_f64() // -> Option<f64> (also handles Integer -> f64)
value.as_str() // -> Option<&str>
value.as_list() // -> Option<&[Value]>
value.as_list_mut() // -> Option<&mut Vec<Value>>
value.as_tuple() // -> Option<&[Value]>
value.as_tuple_mut() // -> Option<&mut Vec<Value>>
value.as_struct() // -> Option<&IndexMap<String, Value>>
value.as_struct_mut() // -> Option<&mut IndexMap<String, Value>>
value.as_integer() // -> Option<&UzonInteger>
value.as_float() // -> Option<&UzonFloat>let bindings = from_str(r#"name is "Alice" age is 30"#).unwrap();
assert_eq!(bindings["name"].as_str(), Some("Alice"));
assert_eq!(bindings["age"].as_i64(), Some(30));
assert_eq!(bindings["age"].as_f64(), Some(30.0)); // auto-converts
assert_eq!(bindings["name"].as_i64(), None); // type mismatch -> NoneBracket indexing for convenient access. Returns &Value::Null for missing keys or out-of-bounds indices — never panics.
// Struct: value["key"]
let name = &value["name"]; // -> &Value::String("Alice") or &Value::Null
// List/Tuple: value[index]
let first = &value[0]; // -> &Value or &Value::Null
// Chained indexing
let host = &config["server"]["host"];let config = uzon!({
"server": {"host": "localhost", "port": 8080},
"tags": ["web", "api"]
});
assert_eq!(config["server"]["host"], "localhost");
assert_eq!(config["tags"][0], "web");
assert_eq!(config["missing"], Value::Null); // no panic
assert_eq!(config["tags"][99], Value::Null); // no panicReturn Option<&Value> instead of &Value::Null:
value.get("key") // -> Option<&Value> (structs)
value.get_index(0) // -> Option<&Value> (lists/tuples)
value.get_mut("key") // -> Option<&mut Value> (structs)
value.get_index_mut(0) // -> Option<&mut Value> (lists/tuples)Navigate nested structures with dot-separated paths:
value.get_path("server.host") // struct key navigation
value.get_path("items.0") // numeric segments index into lists
value.get_path("a.b.c.0.d") // mixed struct/list navigationlet config = uzon!({
"server": {"host": "localhost", "port": 8080},
"items": ["a", "b", "c"]
});
assert_eq!(config.get_path("server.host"), Some(&Value::from("localhost")));
assert_eq!(config.get_path("items.1"), Some(&Value::from("b")));
assert_eq!(config.get_path("missing.path"), None);In-place modification of struct and list values:
// Struct: insert / remove
value.insert("key", 42); // add or update field, returns old value
value.remove("key"); // remove field, returns removed value
// List: push / pop
value.push(42); // append to list
value.pop(); // remove and return last elementlet mut config = uzon!({"host": "localhost", "port": 8080});
config.insert("debug", true); // add field
config.insert("port", 3000); // update field
config.remove("debug"); // remove field
let mut items = uzon!([1, 2, 3]);
items.push(4); // [1, 2, 3, 4]
let last = items.pop(); // Some(Value::int(4))Mutable access for in-place updates:
if let Some(v) = config.get_mut("port") {
*v = Value::int(9090);
}
if let Some(list) = items.as_list_mut() {
list.reverse();
}Recursively merge two struct values:
value.merge(other);- Matching struct fields are merged recursively
- Non-struct fields in
otheroverwrite fields inself - Fields only in
otherare added - Fields only in
selfare kept - Union/TaggedUnion wrappers are unwrapped before merging
let mut base = uzon!({
"server": {"host": "localhost", "port": 8080},
"debug": false
});
let overlay = uzon!({
"server": {"port": 443, "tls": true},
"debug": true
});
base.merge(overlay);
// server.host = "localhost" (kept from base)
// server.port = 443 (overwritten)
// server.tls = true (added)
// debug = true (overwritten)JSON-like syntax for building values:
use uzon::uzon;
// Primitives
uzon!(null) // Value::Null
uzon!(true) // Value::Bool(true)
uzon!(42) // Value::Integer
uzon!(3.14) // Value::Float
uzon!("hello") // Value::String
// Collections
uzon!([1, 2, 3]) // Value::List
uzon!((1, "two", true)) // Value::Tuple
uzon!({"key": "value"}) // Value::Struct
// Nested
uzon!({
"name": "Alice",
"scores": [95, 88, 92],
"address": {
"city": "Seoul"
}
})
// Variables
let name = "Bob";
let age = 25;
uzon!({"name": name, "age": age})Fluent API for building structs:
let user = Value::struct_builder()
.field("name", "Alice")
.field("age", 30)
.field("scores", vec![Value::int(95), Value::int(88)])
.field("address", Value::struct_builder()
.field("city", "Seoul")
.build())
.build();| Rust type | Value variant |
|---|---|
bool |
Bool |
i32, i64, i128, u32, u64 |
Integer |
f32, f64 |
Float |
&str, String |
String |
Vec<Value> |
List |
IndexMap<String, Value> |
Struct |
(Value, Value) |
Tuple (2 elements) |
(Value, Value, Value) |
Tuple (3 elements) |
let v: Value = 42.into();
let v: Value = "hello".into();
let v: Value = vec![Value::int(1), Value::int(2)].into();Fallible conversions that consume the value:
| Target | Source variant(s) | Notes |
|---|---|---|
bool |
Bool |
|
i64 |
Integer, BigInteger |
Errors if out of range |
i128 |
Integer, BigInteger |
Errors if BigInteger out of range |
u64 |
Integer, BigInteger |
Errors if negative or out of range |
f64 |
Float, Integer, BigInteger |
Integer auto-converts |
String |
String |
|
Vec<Value> |
List, Tuple |
|
IndexMap<String, Value> |
Struct |
let n: i64 = Value::int(42).try_into().unwrap();
let f: f64 = Value::int(10).try_into().unwrap(); // auto-converts to 10.0
let s: String = Value::from("hi").try_into().unwrap();Convert &Value without consuming:
| Target | Source variant(s) |
|---|---|
bool |
Bool |
i64 |
Integer, BigInteger |
i128 |
Integer, BigInteger |
f64 |
Float, Integer, BigInteger |
&str |
String |
let value = Value::int(42);
let n: i64 = (&value).try_into().unwrap();
// value is still usable
println!("{value}");pub struct ValueConversionError {
pub from: &'static str, // e.g., "integer"
pub to: &'static str, // e.g., "bool"
}
// Display: "cannot convert integer to bool"Standard Rust operators between Value instances:
| Operator | Types | Notes |
|---|---|---|
+ |
Integer, Float, String | String concatenation via + |
- |
Integer, Float | |
* |
Integer, Float | |
/ |
Integer, Float | Integer division truncates |
% |
Integer, Float | |
- (unary) |
Integer, Float |
Mixed Integer + Float promotes to Float.
Value::int(10) + Value::int(3) // -> int(13)
Value::float(1.5) + Value::float(2) // -> float(3.5)
Value::int(5) + Value::float(0.5) // -> float(5.5)
Value::from("he") + Value::from("llo") // -> "hello"
-Value::int(42) // -> int(-42)Primitives work on either side:
Value::int(10) + 5 // -> int(15)
5 + Value::int(10) // -> int(15)
Value::float(1.0) + 0.5 // -> float(1.5)
Value::from("hi") + "!" // -> "hi!"
"hi" + Value::from("!") // -> "hi!"Supported primitive types: i32, i64, i128, u32, u64, f32, f64, &str.
Return Result<Value, ValueArithmeticError> instead of panicking:
value.checked_add(&other) // -> Result<Value, ValueArithmeticError>
value.checked_sub(&other)
value.checked_mul(&other)
value.checked_div(&other) // errors on division by zero
value.checked_rem(&other) // errors on division by zero
value.checked_neg() // errors on i128::MIN negation// Overflow detection
let max = Value::int(i128::MAX);
assert!(max.checked_add(&Value::int(1)).is_err());
// Division by zero
assert!(Value::int(10).checked_div(&Value::int(0)).is_err());
// Type mismatch
assert!(Value::int(1).checked_add(&Value::Bool(true)).is_err());Value::int(42) == Value::int(42) // true
Value::from("hi") == Value::from("hi") // true
Value::Null == Value::Null // true
Value::int(1) == Value::float(1.0) // false (different types)Value::int(42) == 42 // i32
Value::int(42) == 42i64 // i64
Value::int(42) == 42i128 // i128
Value::int(42) == 42u32 // u32
Value::int(42) == 42u64 // u64
Value::float(3.14) == 3.14 // f64
Value::Bool(true) == true // bool
Value::from("hi") == "hi" // &str
Value::from("hi") == String::from("hi") // StringOrdering for numeric, string, and bool values. Mixed int/float comparison promotes to float.
Value::int(1) < Value::int(2) // true
Value::float(1.0) < Value::float(2) // true
Value::int(1) < Value::float(1.5) // true (cross-type)
Value::from("a") < Value::from("b") // true (lexicographic)
Value::Bool(false) < Value::Bool(true) // true
// Incompatible types -> None
Value::int(1).partial_cmp(&Value::Bool(true)) // NoneValue::int(1) < 2 // i32
Value::int(10) > 5i64 // i64
Value::float(1.0) < 2.0 // f64
Value::from("a") < "b" // &strfor item in &value {
// item: &Value
}| Value type | Yields |
|---|---|
List |
each element |
Tuple |
each element |
Struct |
each value (insertion order) |
| Others | empty iterator |
for item in value {
// item: Value (consumed)
}if let Some(fields) = value.iter_fields() {
for (key, val) in fields {
// key: &String, val: &Value
}
}value.len() // -> Option<usize>
// List -> Some(element count)
// Tuple -> Some(element count)
// Struct -> Some(field count)
// Others -> Nonelet users = uzon!([
{"name": "Alice", "score": 95},
{"name": "Bob", "score": 82}
]);
// Filter and process
let high_scorers: Vec<&str> = (&users).into_iter()
.filter(|u| u["score"].as_i64().unwrap_or(0) > 85)
.filter_map(|u| u["name"].as_str())
.collect();Value implements both serde::Serialize and serde::Deserialize.
use uzon::from_value;
#[derive(serde::Deserialize)]
struct Config {
host: String,
port: u16,
debug: bool,
}
let value = uzon!({"host": "localhost", "port": 8080, "debug": true});
let config: Config = from_value(value).unwrap();One-step parse and deserialize:
use uzon::from_str_as;
let config: Config = from_str_as(r#"
host is "localhost"
port is 8080
debug is true
"#).unwrap();let value = uzon!({"name": "Alice", "scores": [95, 88]});
let json = serde_json::to_string(&value).unwrap();
// -> {"name":"Alice","scores":[95,88]}Serialization rules:
| UZON type | Serialized as |
|---|---|
Null, Undefined |
null |
Bool |
JSON boolean |
Integer |
JSON number (i64 or i128) |
BigInteger |
i128 if fits, otherwise string |
Float |
JSON number (f64) |
String |
JSON string |
List, Tuple |
JSON array |
Struct |
JSON object |
Enum |
JSON string (variant name) |
Union, TaggedUnion |
inner value |
Function |
null |
let value: Value = serde_json::from_str(r#"{"name": "Alice", "age": 30}"#).unwrap();
assert_eq!(value["name"], "Alice");
assert_eq!(value["age"], 30);pub struct DeError(String);
// implements Display, Error, serde::de::Error, serde::ser::ErrorConvert values back to UZON text:
use std::collections::BTreeMap;
use uzon::{to_string, to_string_with_options, StringifyOptions, Value};
// Default formatting (4-space indent, inline up to 4 fields)
let text = to_string(&bindings);
// Custom formatting
let opts = StringifyOptions {
indent: 2,
inline_threshold: 8,
};
let text = to_string_with_options(&bindings, &opts);| Field | Type | Default | Description |
|---|---|---|---|
indent |
usize |
4 |
Spaces per indentation level |
inline_threshold |
usize |
4 |
Max struct fields for single-line format |
UZON supports faithful roundtripping:
let source = r#"name is "Alice" age is 30"#;
let parsed = from_str(source).unwrap();
let output = to_string(&parsed);
let reparsed = from_str(&output).unwrap();
assert_eq!(parsed, reparsed);All parsing functions return Result<T, UzonError>:
pub enum UzonError {
Syntax { message, location, import_trace }, // lexer/parser errors
Type { message, location, import_trace }, // type annotation errors
Runtime { message, location, import_trace }, // evaluation errors
Circular { message, location, import_trace }, // circular reference
}
pub struct Location {
pub line: usize, // 1-based
pub col: usize, // 1-based, Unicode scalar count
pub filename: Option<String>,
}
pub type Result<T> = std::result::Result<T, UzonError>;match from_str("x is 1 ++") {
Ok(bindings) => { /* use bindings */ }
Err(e) => eprintln!("Error: {e}"),
// "SyntaxError at 1:10: unexpected token: Eof ''"
}The examples/ directory contains 12 runnable examples covering every aspect of the API:
cargo run --example 01_parsing # Parsing UZON strings
cargo run --example 02_accessing_values # Accessors, indexing, path navigation
cargo run --example 03_building_values # uzon! macro, StructBuilder, From/Into
cargo run --example 04_mutation # insert, remove, push, pop, merge
cargo run --example 05_arithmetic # Operators, checked arithmetic, primitives
cargo run --example 06_comparison # PartialEq, PartialOrd, sorting
cargo run --example 07_iteration # IntoIterator, iter_fields, len
cargo run --example 08_type_conversion # TryFrom/TryInto, From/Into, to_plain
cargo run --example 09_serde # from_value, from_str_as, JSON interop
cargo run --example 10_stringify # to_string, StringifyOptions, roundtrip
cargo run --example 11_uzon_types # Enums, typed numerics, tagged unions
cargo run --example 12_evaluator # Expressions, functions, std libraryMIT