Types
Every column in a .decl gets a type. FlowLog is statically typed — the compiler catches type mismatches before anything runs.
Numeric types
Signed integers
Your everyday integers. Pick the smallest size that fits your data:
| Type | Rust equivalent | Range |
|---|---|---|
int8 | i8 | -128 to 127 |
int16 | i16 | -32 768 to 32 767 |
int32 | i32 | -2.1 × 109 to 2.1 × 109 |
int64 | i64 | -9.2 × 1018 to 9.2 × 1018 |
.decl Score(student_id: int32, grade: int32)
Unsigned integers
When you know values are never negative:
| Type | Rust equivalent | Range |
|---|---|---|
uint8 | u8 | 0 to 255 |
uint16 | u16 | 0 to 65 535 |
uint32 | u32 | 0 to 4.3 × 109 |
uint64 | u64 | 0 to 1.8 × 1019 |
.decl Pixel(x: uint16, y: uint16, color: uint32)
Floats
Floating-point numbers, wrapped in OrderedFloat under the hood so they can be hashed and compared correctly (no NaN surprises):
| Type | Rust equivalent | Precision |
|---|---|---|
f32 | f32 | ~7 decimal digits |
f64 | f64 | ~15 decimal digits |
.decl Measurement(sensor: int32, value: f64)
String
The string type holds UTF-8 text. String constants use double quotes:
.decl Name(id: int32, label: string)
Name(1, "alice").
Boolean
The bool type is True or False — capitalized. Lowercase true won't work:
.decl Flag(id: int32, active: bool)
Flag(1, True).
Using types in declarations
Every column needs an explicit type — no inference, no defaults:
.decl Edge(src: int32, dst: int32)
.decl Person(id: int64, name: string, active: bool)
.decl Sensor(id: uint32, reading: f64, label: string)
Type checking
FlowLog catches type errors at compile time in these cases:Joins
When a variable appears in multiple atoms, its type must agree everywhere. If Edge.dst is int32 and Node.id is int64, joining on the same variable is a type error:
.decl Edge(src: int32, dst: int32)
.decl Node(id: int32, label: string)
// Works: y is int32 in both Edge and Node
Match(x, label) :- Edge(x, y), Node(y, label).
Change Node.id to int64 and the compiler will complain — y can't be two types at once.
Floats work as join keys too, thanks to OrderedFloat:
.decl A(x: f32, y: f32)
.decl B(x: f32, z: f64)
// x is f32 in both — valid join
AB(x, y, z) :- A(x, y), B(x, z).
Rule heads
All rules deriving the same relation must produce matching types:
.decl Route(src: int32, dst: int32, cost: int32)
// Both rules produce (int32, int32, int32) — good
Route(x, y, c) :- Edge(x, y), Weight(x, y, c).
Route(x, z, c) :- Route(x, y, _), Edge(y, z), Weight(y, z, c).
What FlowLog does not check
Integer and float constant overflow (e.g., stuffing 999 into a uint8) and comparison type mismatches (e.g., int32 > int64) are not caught by FlowLog. The downstream Rust compiler will catch these for you — so you'll still get an error, just a less friendly one.