User-Defined Functions
FlowLog supports **user-defined functions (UDFs)** that call into Rust code from within rules. This lets you extend the language with custom scalar logic that cannot be expressed in pure Datalog.Declaring a UDF
Use .extern fn to declare a UDF's signature:
.extern fn function_name(param1: type1, param2: type2) -> return_type
Example:
.extern fn hash_pair(x: int32, y: int32) -> int64
Parameters and return types use the same data types as relation columns.
Using UDFs in rule heads
A UDF call can appear as a head argument, computing 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).
Using UDFs as body predicates
A UDF call in the rule body acts as a boolean predicate — the rule fires only when the function 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).
Negated UDF predicates are also supported — prefix with !:
InvalidEdge(x, y) :- Edge(x, y), !is_valid(x, y).
Writing the implementation
UDF implementations are written in a standalone Rust source file. Each .extern fn declared in your program must have a corresponding pub fn in this file, using the equivalent Rust types:
For example, given the declaration:
.extern fn hash_pair(x: int32, y: int32) -> int64
Create a Rust file (e.g. my_udfs.rs):
pub fn hash_pair(x: i32, y: i32) -> i64 {
((x as i64) << 32) | (y as i64)
}
The file must compile as a standalone Rust library — do not include fn main() or module declarations.
Passing the UDF file to the compiler
Use the --udf-file flag to provide your Rust source file when compiling:
$ flowlog my_program.dl --udf-file my_udfs.rs -o output
The compiler copies your file into the generated Cargo project as src/udf.rs and calls your functions via udf::function_name().