# How fff.nvim's Rust Backend Works: Architecture and FFI Bridge

> Explore fff.nvim's Rust backend architecture and FFI bridge. Discover how native Rust code powers instant fuzzy finding for large codebases while maintaining Neovim responsiveness.

- Repository: [Dmitriy Kovalenko/fff.nvim](https://github.com/dmtrKovalenko/fff.nvim)
- Tags: architecture
- Published: 2026-04-04

---

**fff.nvim delegates file-system scanning, pattern matching, and result streaming to a native Rust library invoked via C-FFI, enabling near-instant fuzzy finding across large codebases while keeping Neovim's UI responsive.**

fff.nvim is a Neovim fuzzy finder plugin that leverages a high-performance Rust backend to handle search operations. The architecture separates UI concerns in Lua from computational heavy lifting in Rust, connected through a thin C-FFI bridge. Understanding how fff.nvim's Rust backend works reveals why the plugin achieves sub-second search results even in repositories containing millions of lines of code.

## The Three-Component Architecture

The Rust codebase is organized into three specialized crates that handle distinct phases of the search pipeline.

### Query Parser (`fff-query-parser`)

The **query parser** transforms user input strings into structured search constraints. Located in [`crates/fff-query-parser/src/parser.rs`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/crates/fff-query-parser/src/parser.rs) and [`crates/fff-query-parser/src/config.rs`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/crates/fff-query-parser/src/config.rs), this component parses fff-style queries like `"src/**/*.rs:42"` into a `Query` struct containing:

- **Glob patterns** (`GlobSet` from the `globset` crate) for path matching
- **Line number constraints** (e.g., `:42` for specific lines)
- **Exclusion rules** (e.g., `!node_modules/**`)

### Grepping Engine (`fff-grep`)

The **grepping engine** orchestrates file system traversal and pattern matching. Implemented across [`crates/fff-grep/src/searcher/core.rs`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/crates/fff-grep/src/searcher/core.rs), [`crates/fff-grep/src/searcher/glue.rs`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/crates/fff-grep/src/searcher/glue.rs), and [`crates/fff-grep/src/matcher.rs`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/crates/fff-grep/src/matcher.rs), this component:

1. Uses `ignore::WalkBuilder` to walk directories while respecting `.gitignore` files
2. Streams file contents via `BufReader` to minimize memory usage
3. Applies fuzzy-matching algorithms in [`matcher.rs`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/matcher.rs) that toggle between "plain grep" and "fuzzy path" modes depending on the query structure

### C-FFI Bridge (`fff-c`)

The **C-FFI bridge** exposes Rust functionality to Lua through a C-compatible API. Defined in [`crates/fff-c/src/lib.rs`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/crates/fff-c/src/lib.rs) and [`crates/fff-c/include/fff.h`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/crates/fff-c/include/fff.h), this layer compiles into a shared object (`.so` or `.dylib`) that Neovim loads at runtime via `require('fff')`.

## From Lua to Rust: The Execution Flow

When you execute `:Fff` or call `fff.search()`, the request traverses through four distinct layers:

1. **Lua Entry Point** – The [`plugin/fff.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/plugin/fff.lua) file loads the shared library and builds the query string.
2. **FFI Crossing** – Lua passes the query to the exported C function `fff_search` defined in [`crates/fff-c/src/lib.rs`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/crates/fff-c/src/lib.rs).
3. **Parsing Phase** – `fff_query_parser::parse` converts the raw string into a structured `Query` object.
4. **Search Execution** – [`searcher/core.rs`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/searcher/core.rs) drives the directory walk, delegating line-by-line matching to [`matcher.rs`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/matcher.rs) and yielding results through the `Sink` trait.
5. **Result Marshaling** – Matches convert from Rust `Vec<Match>` to C-compatible `*mut fff_match_t` buffers, then back to Lua tables consumed by [`lua/fff/picker_ui.lua`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/lua/fff/picker_ui.lua).

## Key Implementation Details

### File System Walking with `ignore`

The backend leverages the `ignore` crate's `WalkBuilder` for efficient directory traversal. Unlike naive recursion, this respects project-level ignore patterns (`.gitignore`, `.fffignore`) and utilizes parallel walking where possible. The configuration logic in [`crates/fff-grep/src/searcher/glue.rs`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/crates/fff-grep/src/searcher/glue.rs) wires these constraints into the walker before streaming begins.

### Synchronous Callbacks with Asynchronous Wrappers

While the Rust search itself runs synchronously, fff.nvim wraps calls inside `vim.loop` threads to prevent UI blocking. The Lua side registers a callback (`picker_ui.open_with_callback`) that triggers when `fff_search` returns its C-compatible buffer. This architecture keeps Neovim responsive even during deep recursive searches.

### Pre-Built Binary Distribution

The project ships pre-compiled binaries for Linux (`packages/fff-bin-linux-x64-gnu`) and macOS (`packages/fff-bin-darwin-x64`), eliminating the need for users to install Rust toolchains. Local compilation remains supported for custom extensions, allowing developers to modify [`matcher.rs`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/matcher.rs) or add additional file-type filters in [`config.rs`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/config.rs).

## Code Examples

### Invoking the Rust Search from Lua

```lua
local query = "src/**/*.rs:10"
local function on_result(matches, _, location)
  -- `matches` is a list of {path = "...", line = 10, text = "..."}
  require('fff.picker_ui').show(matches, location)
end

require('fff').search(query, on_result)

```

### Exported C Function in [`crates/fff-c/src/lib.rs`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/crates/fff-c/src/lib.rs)

```rust
#[no_mangle]
pub extern "C" fn fff_search(
    query: *const c_char,
    callback: extern "C" fn(*const fff_match_t, usize),
) {
    let q = unsafe { CStr::from_ptr(query) }.to_string_lossy();
    let parsed = query_parser::parse(&q);
    let matches = grep::search(parsed);
    // Convert matches to C structs and invoke the callback
    let c_matches = ffi::to_ffi(matches);
    callback(c_matches.as_ptr(), c_matches.len());
}

```

### Workspace Configuration

```toml
[workspace]
members = [
    "crates/fff-c",
    "crates/fff-grep",
    "crates/fff-query-parser",
]

[dependencies]
ignore = "0.4"
globset = "0.4"

```

## Summary

- **fff.nvim's Rust backend** consists of three crates: `fff-query-parser` for query strings, `fff-grep` for file system traversal and matching, and `fff-c` for Lua interoperability.
- **File paths matter**: [`crates/fff-grep/src/searcher/core.rs`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/crates/fff-grep/src/searcher/core.rs) drives the walk, while [`crates/fff-grep/src/matcher.rs`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/crates/fff-grep/src/matcher.rs) implements the fuzzy algorithm.
- **FFI bridge**: The `fff_search` function in [`crates/fff-c/src/lib.rs`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/crates/fff-c/src/lib.rs) exposes Rust functionality to Lua through C-compatible pointers.
- **Performance**: The `ignore` crate and `BufReader` streaming enable efficient searching of large repositories without blocking the Neovim UI.
- **Extensibility**: Pre-built binaries cover major platforms, but the workspace structure supports local Rust modifications for custom behavior.

## Frequently Asked Questions

### How does fff.nvim communicate between Lua and Rust?

fff.nvim uses a C Foreign Function Interface (FFI) bridge compiled into a shared object. The `fff-c` crate exposes functions like `fff_search` that accept C strings and callbacks, which Lua loads via `require('fff')` and invokes using Neovim's LuaJIT FFI capabilities. This allows Lua to pass query strings to Rust and receive structured match data back as Lua tables.

### What Rust crates power the file system walking in fff.nvim?

The backend relies on the `ignore` crate for directory traversal, which provides `ignore::WalkBuilder` to respect `.gitignore` patterns and `.fffignore` rules. For pattern matching, it uses the `globset` crate to compile glob patterns into efficient `GlobSet` matchers that filter paths during the walk.

### Is the Rust search synchronous or asynchronous?

The Rust code itself operates synchronously through the `fff_search` export function. However, fff.nvim wraps these calls inside `vim.loop` threads on the Lua side, making the overall user experience asynchronous. The UI remains responsive because `picker_ui.open_with_callback` handles results only after the Rust thread completes its search.

### Can I modify the fuzzy matching algorithm?

Yes. Because the core logic resides in [`crates/fff-grep/src/matcher.rs`](https://github.com/dmtrKovalenko/fff.nvim/blob/main/crates/fff-grep/src/matcher.rs), you can fork the repository and modify the matching implementation. After changes, recompile the workspace using `cargo build --release` in the `crates/fff-c` directory, and Neovim will load your custom shared library instead of the pre-built binary.