Skip to main content

Rust SDK

Raw FFI bindings to the Cactus C API. Auto-generated via bindgen.

Installation

Add to your Cargo.toml:
[dependencies]
cactus-sys = { path = "rust/cactus-sys" }
Build requirements:
  • CMake
  • C++20 compiler
  • On macOS: Xcode command line tools
  • On Linux: build-essential, libcurl4-openssl-dev, libclang-dev

Usage

All functions mirror the C API. The Rust bindings provide direct FFI access to all Cactus Engine functionality.

Basic Example

use cactus_sys::*;
use std::ffi::{CString, CStr};
use std::ptr;

unsafe {
    // Initialize model
    let model_path = CString::new("/path/to/model").unwrap();
    let model = cactus_init(model_path.as_ptr(), ptr::null(), false);
    
    if model.is_null() {
        let error = cactus_get_last_error();
        if !error.is_null() {
            eprintln!("Error: {}", CStr::from_ptr(error).to_str().unwrap());
        }
        return;
    }
    
    // Prepare messages
    let messages = CString::new(
        r#"[{"role":"user","content":"What is 2+2?"}]"#
    ).unwrap();
    
    // Complete
    let result = cactus_complete(
        model,
        messages.as_ptr(),
        ptr::null(),  // options
        ptr::null(),  // tools
        None,         // callback
    );
    
    if !result.is_null() {
        let response = CStr::from_ptr(result).to_str().unwrap();
        println!("Response: {}", response);
    }
    
    // Cleanup
    cactus_destroy(model);
}

Transcription Example

use cactus_sys::*;
use std::ffi::{CString, CStr};
use std::ptr;

unsafe {
    let model_path = CString::new("/path/to/whisper-model").unwrap();
    let model = cactus_init(model_path.as_ptr(), ptr::null(), false);
    
    let audio_path = CString::new("/path/to/audio.wav").unwrap();
    let result = cactus_transcribe(
        model,
        audio_path.as_ptr(),
        ptr::null(),  // prompt
        ptr::null(),  // options
        None,         // callback
        ptr::null(),  // pcm_data
        0,            // pcm_len
    );
    
    if !result.is_null() {
        let transcription = CStr::from_ptr(result).to_str().unwrap();
        println!("Transcription: {}", transcription);
    }
    
    cactus_destroy(model);
}

Embeddings Example

use cactus_sys::*;
use std::ffi::CString;
use std::ptr;
use std::slice;

unsafe {
    let model_path = CString::new("/path/to/model").unwrap();
    let model = cactus_init(model_path.as_ptr(), ptr::null(), false);
    
    let text = CString::new("Hello, world!").unwrap();
    let mut embedding_len: usize = 0;
    let embedding_ptr = cactus_embed(
        model,
        text.as_ptr(),
        true,  // normalize
        &mut embedding_len as *mut usize,
    );
    
    if !embedding_ptr.is_null() {
        let embedding = slice::from_raw_parts(embedding_ptr, embedding_len);
        println!("Embedding dimension: {}", embedding_len);
        println!("First few values: {:?}", &embedding[..5]);
    }
    
    cactus_destroy(model);
}

Vector Index Example

use cactus_sys::*;
use std::ffi::{CString, CStr};
use std::ptr;

unsafe {
    let index_dir = CString::new("/path/to/index").unwrap();
    let index = cactus_index_init(index_dir.as_ptr(), 384);
    
    if index.is_null() {
        eprintln!("Failed to initialize index");
        return;
    }
    
    // Add documents
    let ids = vec![1i32, 2i32];
    let doc1 = CString::new("First document").unwrap();
    let doc2 = CString::new("Second document").unwrap();
    let docs = vec![doc1.as_ptr(), doc2.as_ptr()];
    
    let embedding1 = vec![0.1f32; 384];
    let embedding2 = vec![0.2f32; 384];
    let embeddings = vec![embedding1.as_ptr(), embedding2.as_ptr()];
    
    cactus_index_add(
        index,
        ids.as_ptr(),
        docs.as_ptr(),
        embeddings.as_ptr(),
        ptr::null(),  // metadatas
        2,            // count
    );
    
    // Query
    let query_embedding = vec![0.15f32; 384];
    let results = cactus_index_query(
        index,
        query_embedding.as_ptr(),
        ptr::null(),  // options
    );
    
    if !results.is_null() {
        let results_json = CStr::from_ptr(results).to_str().unwrap();
        println!("Results: {}", results_json);
    }
    
    cactus_index_destroy(index);
}

API Reference

All functions mirror the C API documented in the C API Reference. Key functions include:

Init / Lifecycle

pub fn cactus_init(model_path: *const c_char, corpus_dir: *const c_char, cache_index: bool) -> *mut c_void;
pub fn cactus_destroy(model: *mut c_void);
pub fn cactus_reset(model: *mut c_void);
pub fn cactus_stop(model: *mut c_void);
pub fn cactus_get_last_error() -> *const c_char;

Completion

pub fn cactus_complete(
    model: *mut c_void,
    messages_json: *const c_char,
    options_json: *const c_char,
    tools_json: *const c_char,
    callback: Option<unsafe extern "C" fn(*const c_char, u32, *mut c_void)>,
) -> *const c_char;

Transcription

pub fn cactus_transcribe(
    model: *mut c_void,
    audio_path: *const c_char,
    prompt: *const c_char,
    options_json: *const c_char,
    callback: Option<unsafe extern "C" fn(*const c_char, u32, *mut c_void)>,
    pcm_data: *const u8,
    pcm_len: usize,
) -> *const c_char;

Embeddings

pub fn cactus_embed(model: *mut c_void, text: *const c_char, normalize: bool, out_len: *mut usize) -> *const f32;
pub fn cactus_image_embed(model: *mut c_void, image_path: *const c_char, out_len: *mut usize) -> *const f32;
pub fn cactus_audio_embed(model: *mut c_void, audio_path: *const c_char, out_len: *mut usize) -> *const f32;

Vector Index

pub fn cactus_index_init(index_dir: *const c_char, embedding_dim: i32) -> *mut c_void;
pub fn cactus_index_destroy(index: *mut c_void);
pub fn cactus_index_add(
    index: *mut c_void,
    ids: *const i32,
    documents: *const *const c_char,
    embeddings: *const *const f32,
    metadatas: *const *const c_char,
    count: usize,
) -> i32;
pub fn cactus_index_query(
    index: *mut c_void,
    embedding: *const f32,
    options_json: *const c_char,
) -> *const c_char;

Testing

export CACTUS_MODEL_PATH=/path/to/model
export CACTUS_STT_MODEL_PATH=/path/to/whisper-model
cargo test --manifest-path rust/Cargo.toml -- --nocapture

Safety Considerations

All functions are marked as unsafe because they interact with C FFI. When using this crate:
  • Always check returned pointers for null before dereferencing
  • Ensure strings are valid UTF-8 and null-terminated
  • Handle memory management carefully (initialize and destroy models)
  • Use proper error checking with cactus_get_last_error()

See Also

C API Reference

Full C API reference that the Rust bindings wrap

Python SDK

Python bindings with higher-level wrappers

Swift SDK

Swift bindings for Apple platforms

Kotlin SDK

Kotlin bindings for Android

Build docs developers (and LLMs) love