datafrog: a Datalog engine you drive yourself from Rust
A lightweight Datalog engine in Rust
At a glance
- What is it?
- datafrog is a small Datalog engine written in Rust with no runtime of its own: you build the relations, apply the update rules in a loop, and stop when the iteration stops changing. It was written by Frank McSherry and later moved into the rust-lang-nursery organisation, and it is Apache-2.0 or MIT.
- Who is it for?
- datafrog suits a Rust program that needs one specific analytic query, a graph reachability computation or a transitive closure, and does not want a database, a query language, or a separate process. It does not suit a project that wants to load data and ask ad hoc questions, because there is no query language and no evaluator: the rules are Rust code you write and re-apply.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 65 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
No runtime means you own the fixpoint loop
datafrog is described as a lightweight Datalog engine intended to be embedded in other Rust programs, and the second sentence is the whole design.
Datafrog has no runtime, and relies on you to build and repeatedly apply the update rules.
That is the opposite of how a database or a query engine works. There is no server, no file format, no query parser, no planner. You get data structures and a set of operations, and you write the loop yourself.
The documentation's framing is that datafrog tries to help you do this correctly. That word correctly is doing real work. Datalog evaluation is a fixpoint computation: you apply rules until nothing changes, and the classic bugs are stopping too early, iterating too long, and mutating a relation while you are reading it. A library that gives you the operations and lets you express the termination condition explicitly removes the class of bug where the engine decides when to stop.
The reachability example shows the shape. You declare an iteration context, create variables for relations, insert initial values, and then loop while the iteration reports that something changed.
// Create a new iteration context, ..
let mut iteration = Iteration::new();
// .. some variables, ..
let nodes_var = iteration.variable::<(u32,u32)>("nodes");
let edges_var = iteration.variable::<(u32,u32)>("edges");
// .. load them with some initial values, ..
nodes_var.insert(nodes.into());
edges_var.insert(edges.into());
// .. and then start iterating rules!
while iteration.changed() {
// nodes(a,c) <- nodes(a,b), edges(b,c)
nodes_var.from_join(&nodes_var, &edges_var, |_b, &a, &c| (c,a));The rule is written as a comment in Datalog syntax, nodes(a,c) <- nodes(a,b), edges(b,c), and the from_join call is its translation: join the nodes relation with the edges relation on the middle element, and project the outer two.
The closure signature is worth pausing on. It takes the join key first, then the non-joined values from each side, and returns the new tuple. The key being passed in separately means you never have to destructure it out of a tuple, which is what makes the call compile when the key is in the middle position as it is here.
The other thing to notice is that the loop calls from_join with the same variables it reads from. That is how the relation grows: each pass joins the current contents with the edges, adding newly reachable pairs, and the changed() call is what tells you to go round again.
Antijoins are the other half of the Datalog vocabulary
A join is the operation everyone knows, and datafrog also provides the negation form, which is where Datalog earns its keep over plain graph traversal.
The repository examples directory contains two files, borrow_check.rs and graspan1.rs. The second is the more interesting name to anyone who has worked on program analysis, since GasPAn is a static analysis tool that answers reachability and security questions about a program by running Datalog rules over the abstract syntax of the source.
That example in the repository is the clearest evidence of what datafrog is for. The rules in a GasPAn style analysis are joins and antijoins over relations such as points-to sets, and an antijoin, a join that excludes matching tuples, is the primitive that expresses may-alias and reachability-not-from conditions. Expressing those in ordinary Rust means writing nested loops with hash lookups and careful attention to which side is being mutated.
The pattern the library offers is differential. Instead of computing a result set from scratch, you express the delta: which tuples are newly true. The iteration context tracks that for you, and the changed() method reports whether the last application produced any new tuples.
The borrow_check.rs example points the other way, towards something a compiler-like tool needs: a check that relates two relations and reports the cases where a property does not hold.
The practical reason to choose a Datalog engine rather than hand-written loops is that the rules are declarative. A GasPAn style analysis is a set of facts and a set of rules, and the order in which you apply them does not matter for the final answer, only the number of passes needed to reach the fixpoint. Writing that as imperative code means you have to get the ordering and the termination right by hand, and when you add a rule later you have to revisit the loop.
Reading the results and the data model behind complete()
Extraction is a single call once the loop has converged.
let reachable: Vec<(u32,u32)> = nodes_var.complete();The name complete() is the right one. The variable holds the accumulated relation, and complete returns the final contents as an ordinary Rust value that you can consume directly. There is no result handle, no cursor, and no lifetime tied to the iteration context.
The variable declaration is the other thing worth understanding.
let nodes_var = iteration.variable::<(u32,u32)>("nodes");
let edges_var = iteration.variable::<(u32,u32)>("edges");A variable is created from the iteration context, typed by a turbofish with the tuple type the relation holds, and given a name. The name is presumably what appears in error messages and in the debugging output, and the type is what the compiler checks your rules against. Both matter: a join between two variables of different tuple types is a type error rather than a runtime surprise.
The initial values are inserted rather than assigned, because in Datalog the base facts are an initial relation and the rules are additions to it. insert takes an Into iterator and folds it into the relation.
The names in the example also hint at the intended scale. The tuples are (u32, u32), which is a pair of node identifiers, and the reachability relation ends up holding reachable pairs. The datafrog crate description says lightweight, and the type parameters are generic over the tuple rather than fixed to a schema, which is what lets the same engine serve a graph analysis and something else entirely.
Proptest in the dev dependencies and a repository with no releases
The package manifest is short, which tells you most of what you need to know about the project's shape.
The package is datafrog at version 2.0.1, edition 2018, with the description Lightweight Datalog engine intended to be embedded in other Rust programs, and keywords datalog and analysis. The licence field is Apache-2.0/MIT, and the repository root carries both LICENSE-APACHE and LICENSE-MIT, so the dual licensing is a choice rather than an accident.
The dev dependencies list contains exactly one crate, proptest at 0.8.7. Property based testing is the right tool for a fixed point engine, because the properties worth asserting are invariants that hold for arbitrary inputs rather than examples, and the failure modes of a semi-naive or incremental Datalog implementation, wrong answers after convergence, duplicate tuples, or a loop that never terminates, are exactly the kind that a hand written example set misses.
There is no regular dependency at all. A Datalog engine with zero runtime dependencies is a genuinely lightweight thing to embed, and it means the crate cannot pull anything into a build that uses it.
Two release facts are worth noting. The repository has no GitHub releases, but it does have a RELEASES.md file at the root, so the release history lives in the repository rather than in GitHub tags. And the last push was on 2026-08-19, with the package version at 2.0.1.
There is one small inconsistency to be aware of. The repository URL in the package manifest points at github.com/rust-lang-nursery/datafrog, and the two badges in the manifest are the is-it-maintained issue resolution and open issues badges, also pointed at the nursery organisation. The code now lives under the rust-lang organisation, so those references are stale. It also means the crate published on crates.io points back at a repository path that is no longer where the source lives, which is a small but real annoyance for anyone trying to follow a link from a dependency list.
The triagebot.toml file at the root is the Rust triage bot configuration, which handles labelling and assigning incoming issues automatically. Combined with CODE_OF_CONDUCT.md, it suggests the project still follows the Rust project conventions for community management.
Where datafrog is the wrong tool
The honest limitation is that datafrog is not a database and not a query engine, and the distance between it and either of those is larger than it may first appear.
If you want to load a dataset and then ask arbitrary questions about it, datafrog cannot do that. There is no query language. Every question you want answered has to be written as a Rust rule, and the set of rules is fixed at compile time. That is a completely reasonable design for an analysis embedded in a compiler or a linter, where the questions are known in advance, and a poor fit for an interactive tool where the questions arrive one at a time from a user.
The second limitation is the embedding requirement. Datafrog is intended to be embedded in other Rust programs. If your application is not in Rust, the engine is not directly usable, and there is no documented binding or server mode in this repository. The reachability example is Rust code with Rust types, and the closure that projects joined tuples is a Rust closure.
The third is that the fixpoint loop is on you, and with it the responsibility for termination. The library tracks whether anything changed, and while iteration.changed() is the idiomatic condition, the shape of the loop is yours. That is the price of the design, and it is a price worth paying only if you want the control.
The alternative approach depends on what you are actually trying to do. For an ad hoc query over stored data, an embedded SQL engine or a graph database gives you a query language and storage, and you accept the dependency and the cost of serialising your data into it. For a one-off reachability computation inside a build, writing the worklist algorithm by hand in Rust is often a hundred lines and no dependency at all, though you lose the declarative rules and the ability to add a rule without touching the loop.
For a full analysis framework where the rule set is the intellectual content, a dedicated Datalog implementation in that ecosystem, the kind the GasPAn example points at, will have rule-level diagnostics and incremental evaluation that datafrog does not attempt.
Editorial conclusion
datafrog suits a Rust program that needs one specific analytic query, a graph reachability computation or a transitive closure, and does not want a database, a query language, or a separate process. It does not suit a project that wants to load data and ask ad hoc questions, because there is no query language and no evaluator: the rules are Rust code you write and re-apply. Before adopting it, read the reachability example and the linked blog post, since the engine's unusual shape, a manual fixpoint loop over from_join and from_antijoin, is not something the type signature makes obvious, and note that the repository URL in the package manifest still points at rust-lang-nursery rather than the rust-lang organisation that hosts the code today.
Frequently asked questions
What is datafrog?
It is a lightweight Datalog engine in Rust intended to be embedded in other Rust programs. It has no runtime of its own and relies on you to build the relations and repeatedly apply the update rules, with a no-runtime-dependencies manifest and a dual Apache-2.0 or MIT licence.
How do I write a reachability query with datafrog?
Create an Iteration, declare variables with iteration.variable for each relation, insert the initial values, then loop while iteration.changed() applying from_join. The example joins a nodes relation with an edges relation on the middle tuple element to compute reachable pairs, and nodes_var.complete() returns the final relation.
Does datafrog have a query language or a server?
No. Datafrog has no runtime, and there is no query language or separate process in the repository. Every rule is written in Rust as a call such as from_join, and the fixpoint loop is written by you.
Who wrote datafrog and where does it live now?
It was initially developed by Frank McSherry and was later transferred to the rust-lang-nursery organization, and it is now hosted under the rust-lang organisation. The repository URL and the is-it-maintained badges in the package manifest still point at the older rust-lang-nursery path.
What testing does the datafrog crate use?
The dev dependencies list a single crate, proptest at 0.8.7, which is property based testing. The repository also carries RELEASES.md rather than using GitHub releases, and the last push was on 2026-08-19 with package version 2.0.1.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/rust-lang-datafrog)Was this useful?