Skip to main content

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 (int32i32, int64i64, 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.