zvec-rust

June 10, 2026 · View on GitHub

CI License

English | 中文

Safe, idiomatic Rust bindings for the zvec vector database.

Features

  • RAII Resource Management — All C resources are automatically freed via Drop
  • Builder Pattern — Fluent APIs for schema, query, and configuration
  • Type Safety — Rust enums for all C constants with compile-time checks
  • Comprehensive Error Handling — All FFI calls return Result<T> with detailed error codes
  • Zero-Copy Where Possible — Minimizes data copying across the FFI boundary
  • Prebuilt Libraries — Automatically downloads prebuilt libzvec_c_api from GitHub Releases; advanced users can override with ZVEC_LIB_DIR

Supported Platforms

PlatformArchitectureCI StatusNotes
macOSARM64 (Apple Silicon)✅ Clippy + TestPrimary development platform
macOSx86_64 (Intel)✅ Clippy + Test
Linuxx86_64✅ Clippy + Test + Fuzz + Coverage + BenchmarkFull CI coverage
LinuxARM64 (AArch64)✅ Clippy + Test + Fuzz + Coverage
Windowsx86_64 (MSVC)✅ Clippy + TestCMake + MSVC toolchain

The dynamic library name varies by platform: libzvec_c_api.dylib (macOS), libzvec_c_api.so (Linux), zvec_c_api.dll (Windows).

Architecture

zvec-rust/
├── zvec-sys/    # Low-level FFI bindings to libzvec_c_api
├── zvec/        # Safe, high-level Rust wrapper
└── fuzz/        # Fuzz testing targets
  • zvec-sys — Raw extern "C" declarations, opaque pointer types, and constants
  • zvec — Safe wrappers with RAII, builders, iterators, and idiomatic Rust APIs

Prerequisites

The Rust SDK depends on the zvec C library (libzvec_c_api). For most users, no manual setup is needed — the build script automatically downloads a prebuilt library from GitHub Releases.

For Regular Users (Zero Setup)

Just add the dependency — the build script handles everything:

[dependencies]
zvec = { git = "https://github.com/zvec-ai/zvec-rust.git", tag = "v0.5.0" }

On first build, build.rs will automatically download the prebuilt libzvec_c_api for your platform from GitHub Releases and set up the library path via rpath.

For Advanced Users (Custom Build)

If you want to build the zvec C library yourself (e.g., for a custom configuration or unsupported platform), set the ZVEC_LIB_DIR environment variable to override the automatic download:

# Build zvec from source
git clone https://github.com/alibaba/zvec.git && cd zvec
mkdir -p build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_C_BINDINGS=ON
make -j$(nproc)

# Point zvec-rust to your custom build
export ZVEC_LIB_DIR=/path/to/zvec/build/lib

Or use the built-in Makefile for local development:

make setup        # Install dev tools + init git submodule
make zvec-build   # Build the zvec C library from submodule
make test-all     # Run all tests

Library Resolution Order

The build script resolves the C library in this order:

  1. ZVEC_LIB_DIR environment variable (highest priority)
  2. Sibling checkout: ../zvec/build/lib
  3. Git submodule: vendor/zvec/build/lib
  4. Vendor directory: vendor/lib/
  5. Prebuilt download: from GitHub Releases (automatic)
  6. Auto-build: clone and build from source (requires git, cmake, C++17 compiler)

Set ZVEC_AUTO_BUILD=0 to disable steps 5 and 6.

Quick Start

use zvec::*;

fn main() -> zvec::Result<()> {
    // 1. Initialize the engine
    initialize(None)?;

    // 2. Define schema
    let schema = CollectionSchema::builder("my_collection")
        // Note 1: use `?` to unwrap the Result returned by FieldSchema::new
        .add_field(FieldSchema::new("id", DataType::String, false, 0)?)
        // Note 2: use `?` to unwrap the Result returned by IndexParams::hnsw
        .add_vector_field(
            "embedding",
            DataType::VectorFp32,
            128,
            IndexParams::hnsw(MetricType::Cosine, 16, 200)?
        )
        .build()?;

    println!("Schema built successfully.");

    // 3. Create and open the collection (the directory will be created if it does not exist)
    // "./data" is the local storage path
    let collection = Collection::create_and_open("./data", &schema, None)?;

    println!(" Collection opened.");

    // 4. Insert data
    let mut doc = Doc::new()?;
    doc.set_pk("doc1");
    doc.add_string("id", "doc1")?;
    // Build a 128-dim vector filled with 0.1
    let vec_data = vec![0.1_f32; 128];
    doc.add_vector_f32("embedding", &vec_data)?;

    // `insert` accepts a slice of &[&Doc]
    collection.insert(&[&doc])?;

    println!("Document inserted.");

    // 5. Vector similarity search
    // Query vector: filled with 0.2
    let query_vec = vec![0.2_f32; 128];
    let query = SearchQuery::new("embedding", &query_vec, 10)?;

    let results = collection.query(&query)?;

    println!("Search Results:");
    for result in &results {
        let pk = result.get_pk().unwrap_or("unknown");

        let score = result.get_score();
        println!("   PK: {}, Score: {:.4}", pk, score);
    }

    // 6. Shutdown the engine
    shutdown()?;

    println!("Test Finished!");
    Ok(())
}

Examples

Run any example with cargo run --example <name>:

ExampleDescription
basicEnd-to-end workflow: schema → insert → query → fetch → delete
schema_builderVarious schema configurations: field types, index types, quantization
vector_searchVector query patterns: simple, builder, filter, output fields, HNSW params
crud_operationsFull CRUD: insert, fetch, update, upsert, delete, stats, flush
config_loggingLibrary configuration: memory limits, thread counts, logging
cargo run --example basic
cargo run --example vector_search

API Overview

Initialization

FunctionDescription
initialize(config)Initialize the library (call once); pass None for defaults
shutdown()Release all resources
version()Get version string
is_initialized()Check initialization status

Use ConfigBuilder to customize memory limits, thread counts, and logging:

let config = ConfigBuilder::new()
    .memory_limit(1024 * 1024 * 1024)
    .num_threads(4)
    .enable_console_log(true)
    .build();
initialize(Some(&config))?;

Schema Definition

let schema = CollectionSchema::builder("name")
    .add_field(FieldSchema::new("field", DataType::String, false, 0)?)
    .add_vector_field("vec", DataType::VectorFp32, 128,
        IndexParams::hnsw(MetricType::Cosine, 16, 200)?)
    .build()?;

Collection Operations

MethodDescription
Collection::create_and_open()Create a new collection
Collection::open()Open an existing collection
collection.insert(&docs)Insert documents
collection.update(&docs)Update documents
collection.upsert(&docs)Insert or update
collection.delete(&pks)Delete by primary keys
collection.delete_by_filter(filter)Delete documents matching a filter expression
collection.query(&query)Vector similarity search
collection.multi_query(&query)Multi-route search with RRF / weighted rerank
collection.fetch(&pks)Fetch by primary keys
collection.fetch_with_options(&pks, fields, include_vector)Fetch with output-field control
collection.create_index(field, params) / drop_index(field)Runtime index management
collection.optimize()Rebuild indexes / merge segments
collection.stats()Get collection statistics
collection.flush()Flush to disk

Document Operations

let mut doc = Doc::new()?;
doc.set_pk("my_pk");
doc.add_string("name", "value")?;
doc.add_i64("count", 42)?;
doc.add_vector_f32("embedding", &[0.1, 0.2, 0.3])?;

// Getters return `Result<Option<T>>` — `?` only unwraps the Result.
// Use `unwrap_or_default()` / `expect(..)` etc. to handle the Option.
let name: Option<String> = doc.get_string("name")?;
let count: Option<i64> = doc.get_i64("count")?;

Vector Query

// Simple query
let query = SearchQuery::new("embedding", &query_vec, 10)?;

// Builder pattern with filters
let query = SearchQuery::builder()
    .field_name("embedding")
    .vector(&query_vec)
    .topk(10)
    .filter("category = 'tech'")
    .output_fields(&["id", "name"])
    .build()?;

Supported Types

CategoryTypes
ScalarBool, Int32, Int64, Uint32, Uint64, Float, Double, String, Binary
VectorVectorFp16, VectorFp32, VectorFp64, VectorInt4, VectorInt8, VectorInt16, VectorBinary32, VectorBinary64
SparseSparseVectorFp16, SparseVectorFp32
ArrayArrayBool, ArrayInt32, ArrayInt64, ArrayUint32, ArrayUint64, ArrayFloat, ArrayDouble, ArrayString, ArrayBinary

Index Types

Available distance metrics: L2, Ip, Cosine, MipsL2.

TypeConstructorDescription
HNSWIndexParams::hnsw(metric, m, ef)Graph index (recommended)
HNSW+QIndexParams::hnsw_with_quantize(...)HNSW with quantization
IVFIndexParams::ivf(metric, nlist, niters, soar)Inverted file index
FlatIndexParams::flat(metric)Brute-force index
InvertIndexParams::invert(range, wildcard)Scalar field index
FTSIndexParams::fts(tokenizer, filters, extra)Full-text search index

Testing

# Using Makefile (recommended — auto-detects library paths)
make test-unit         # Unit tests (no C library required)
make test-integration  # Integration tests (requires C library)
make test-all          # All tests (unit + integration + doc)

# Using cargo directly (requires ZVEC_LIB_DIR / DYLD_LIBRARY_PATH)
cargo test --lib
cargo test --test integration_test

# Fuzz tests (requires nightly)
cargo install cargo-fuzz
cargo +nightly fuzz run fuzz_types -- -max_total_time=60

# Benchmarks
make bench

# Code coverage
cargo install cargo-llvm-cov
./scripts/coverage.sh --html

Keeping in Sync with zvec Core

This SDK tracks the zvec C-API. When the upstream C-API changes:

  1. Update zvec-sys/src/lib.rs with new FFI declarations
  2. Add safe wrappers in the zvec crate
  3. Update integration tests to cover new functionality
  4. Run the full test suite to verify compatibility

The CI pipeline automatically clones the latest zvec and builds the C library, ensuring FFI compatibility on every PR.

Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Ensure all tests pass (cargo test)
  4. Ensure code is formatted (cargo fmt --all -- --check)
  5. Ensure clippy is clean (cargo clippy --workspace --all-targets -- -D warnings)
  6. Submit a pull request

License

Apache-2.0 — see LICENSE.