User-Defined Functions
Pure Datalog can't do everything. When you need custom logic — hashing, string parsing, external lookups — FlowLog lets you call into Rust code via user-defined functions (UDFs).
Declaring a UDF
Use .extern fn to tell FlowLog about your function's signature:
.extern fn hash_pair(x: int32, y: int32) -> int64
Parameters and return types use the same types as relation columns.
UDFs in rule heads
Call a UDF in the head to compute a derived value:
.extern fn hash_pair(x: int32, y: int32) -> int64
.decl Edge(src: int32, dst: int32)
.decl HashedEdge(src: int32, dst: int32, h: int64)
HashedEdge(x, y, hash_pair(x, y)) :- Edge(x, y).
UDFs as body predicates
A UDF returning bool can act as a filter in the body — the rule only fires when it returns true:
.extern fn is_valid(x: int32, y: int32) -> bool
.decl Edge(src: int32, dst: int32)
.decl ValidEdge(src: int32, dst: int32)
ValidEdge(x, y) :- Edge(x, y), is_valid(x, y).
Negate with ! to flip the condition:
InvalidEdge(x, y) :- Edge(x, y), !is_valid(x, y).
Writing the Rust implementation
Create a standalone Rust source file with a pub fn for each .extern fn you declared. Use the equivalent Rust types (int32 → i32, int64 → i64, etc.):
// In your .dl program:
.extern fn hash_pair(x: int32, y: int32) -> int64
// In my_udfs.rs:
pub fn hash_pair(x: i32, y: i32) -> i64 {
((x as i64) << 32) | (y as i64)
}
Keep it simple — no fn main(), no module declarations. Just the functions.
Compiling with UDFs
Pass your Rust file with --udf-file:
$ flowlog my_program.dl --udf-file my_udfs.rs -o output
The compiler bakes your functions into the generated executable. They're called at runtime just like built-in operations.