home / skills / redisearch / redisearch / port-c-module

port-c-module skill

/.skills/port-c-module

This skill guides porting a C module to Rust, outlining analysis, planning, and integration to ensure a safe, idiomatic Rust implementation.

npx playbooks add skill redisearch/redisearch --skill port-c-module

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
3.6 KB
---
name: port-c-module
description: Guide for porting a C module to Rust
disable-model-invocation: true
---

# Port Module Skill

Guide for porting a C module to Rust.

## Arguments
The module name to port should be provided as an argument (e.g., `/port-module triemap`).

Module to port: `$ARGUMENTS`

## Usage
Use this skill when starting to port a C module to Rust.

## Instructions

### 1. Analyze the C Code
First, understand the C module you're porting (look for `$ARGUMENTS.c` and `$ARGUMENTS.h` in `src/`):
- Read the `.c` and `.h` files in `src/`
- Identify what is exposed by the header file:
  - Does the rest of the codebase have access to the inner fields of the data structures defined in this module?
  - Are types always passed by value or by reference? A mix?
  - Can the corresponding Rust types be passed over the FFI boundary?
- Understand data structures and their lifetimes
- Identify which types and functions this module imports from other modules:
  - Determine if those types are implemented in Rust or C
  - If those types are implemented in Rust, identify the relevant Rust crate
  - If those types are implemented in C, understand if it makes sense to port them first to Rust
    or if it's preferable to invoke the C implementation from Rust via FFI
- Note any global state or Redis module interactions
- Identify which tests under `tests/` are relevant to this module

### 2. Define A Porting Plan

Create a `$ARGUMENTS_plan.md` file to outline the steps and decisions for porting the module.
Determine if the C code should be modified, at this stage, to ease the porting process.
For example:
- Introduce getters and setters to avoid exposing inner fields of data structures defined in this module.
- Split the module into smaller, more manageable parts.

### 3. Create the Rust Crate
```bash
cd src/redisearch_rs
cargo new $ARGUMENTS --lib
```

### 4. Implement Pure Rust Logic
- Create idiomatic Rust code
- Add comprehensive tests
  - Ensure that all C/C++ tests have equivalent Rust tests
- Document public APIs with doc comments
- For performance sensitive code, create microbenchmarks using `criterion`
- Use `proptest` for property-based testing where appropriate
- Testing code should be written with the same care reserved to production code

### 5. Compare Rust API with C API
- Review the public API of the new Rust module against the C API in the header file
- Ensure that differences can be bridged by adding appropriate wrappers or adapters
- Go back to step 1 if discovered differences cannot be bridged without a re-design

### 6. Create FFI Wrapper
Create an FFI crate to expose the new Rust module to the C codebase:
```bash
cd src/redisearch_rs/c_entrypoint
cargo new ${ARGUMENTS}_ffi --lib
```

FFI crate should:
- Expose `#[unsafe(no_mangle)] pub extern "C" fn` functions
- Handle null pointers and error cases
- Convert between C and Rust types safely
- Document all unsafe blocks with `// SAFETY:` comments

### 7. Wire Up C Code
- Delete the C header file and its implementation
- Update the rest of the C codebase to import the new Rust header wherever the old C header was used

C header files for Rust FFI crates are auto-generated. No need to use their full path in imports,
use just their name (e.g. `#include $ARGUMENTS.h;` for `${ARGUMENTS}_ffi`)

### 8. Test The Integration
```bash
./build.sh RUN_UNIT_TESTS               # C/C++ unit tests
./build.sh RUN_PYTEST                   # Integration tests
```

## Example: Well-Ported Module
See `src/redisearch_rs/trie_rs/` for a high-quality example:
- Pure Rust implementation with comprehensive docs
- Extensive test coverage
- Clean FFI boundary in `c_entrypoint/trie_ffi/`

Overview

This skill guides porting a C module to Rust for a Redis-based query and indexing engine. It focuses on analyzing the C module, planning the port, implementing idiomatic Rust, creating a safe FFI boundary, and validating integration with existing C code and tests. The goal is a maintainable, well-tested Rust replacement that interoperates cleanly with the surrounding C codebase.

How this skill works

Start by auditing the module's .c and .h to understand exposed types, ownership, and dependencies. Create a porting plan that captures design decisions, API adjustments, and any C-side changes needed to simplify the transition. Implement the module in a Rust crate with tests, benchmarks, and documentation, then add a thin FFI crate that exposes safe extern "C" functions and C headers for the rest of the codebase. Finally, replace the original C entry points and run unit and integration tests to validate behavior.

When to use it

  • When a performance- or safety-critical C module needs modern Rust guarantees.
  • When reducing memory bugs and undefined behavior is a priority.
  • When you need a maintainable implementation with strong tests and docs.
  • When the module has clear boundaries and limited global state.
  • When existing tests and behavior can be reproduced in Rust.

Best practices

  • Map C ownership and lifetimes explicitly before writing Rust types.
  • Prefer pure Rust logic first; keep unsafe/FFI minimal and documented with SAFETY comments.
  • Add or modify C getters/setters if required to avoid exposing internals.
  • Write equivalent unit tests and property tests; add microbenchmarks for hotspots.
  • Generate and ship a clean C header from the FFI crate and handle null/error cases rigorously.

Example use cases

  • Port an inverted-index or trie implementation to get memory-safety and clearer invariants.
  • Reimplement a geospatial or full-text module in Rust while preserving C API compatibility.
  • Replace a vector similarity search kernel with an optimized Rust version and expose it via FFI.
  • Split a large C module into smaller Rust crates, each with its own tests and benchmarks.

FAQ

Should I port dependencies too or call them via FFI?

Evaluate each dependency: if it has a clear Rust equivalent or will benefit from Rust guarantees, port it; otherwise expose it via FFI to minimize work.

How do I handle global state and Redis interactions?

Isolate global state behind safe Rust abstractions, document lifetimes, and keep Redis module interactions in thin, well-documented FFI functions.