Skip to main content

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().