Skip to content

[C API] Refactor C API to be ready for the main branch - #363

Open
rfsaliev wants to merge 5 commits into
dev/c-apifrom
rfsaliev/c-api-refactor
Open

[C API] Refactor C API to be ready for the main branch#363
rfsaliev wants to merge 5 commits into
dev/c-apifrom
rfsaliev/c-api-refactor

Conversation

@rfsaliev

Copy link
Copy Markdown
Member

This pull request makes significant improvements to the C API bindings for Scalable Vector Search, focusing on consistent header layout, improved CMake integration, and modernization of sample code. The changes standardize header locations, introduce versioned headers, update the CMake installation structure, and refactor usage of result and threadpool interfaces in the C API samples.

Important note:

This API refactoring breaks compatibility with existing client code based on the dev/c-api branch

Header and Installation Layout Improvements:

  • All public headers are now located under include/svs/c/ instead of include/svs/c_api/, and installation paths have been updated accordingly for consistency and clarity. [1] [2]
  • A generated version header svs_c_version.h is now included, providing version macros for consumers. This header is configured via CMake and installed as part of the public interface. [1] [2]
  • The CMake configuration uses GNUInstallDirs for standard install locations and sets up public/private include directories correctly for consumers and downstream projects. [1] [2] [3]

Sample Code Modernization and API Updates:

  • All sample code now includes headers from the new svs/c/ path and uses the updated API for search results and threadpool interfaces, improving clarity and correctness. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13]
  • The dynamic and save/load samples now use the new svs_search_results_t struct and its associated functions, and threadpool usage is updated for the new interface. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]

Documentation Additions:

  • A comprehensive new README.md is added for the C API, detailing usage, build instructions, API design, and quickstart examples for end users.

Header Content Updates:

  • The main configuration header now includes the generated version header, ensuring version macros are always available to consumers.

Summary of the most important changes:

Header and Installation Structure:

  • All public headers moved to include/svs/c/, and installation paths updated to match, replacing the old c_api directory. [1] [2]
  • CMake configuration improved: uses GNUInstallDirs, sets up correct include paths for build/install, and properly exports the versioned header. [1] [2] [3]
  • Added generated version header svs_c_version.h with version macros, configured and installed via CMake.

Sample and API Modernization:

Documentation:

  • Added a detailed README.md for the C API, including build instructions, usage, and API overview.

Header Content:

  • The main config header now always includes the generated version header, ensuring version macros are available.

- Updated ThreadPoolBuilder to ensure custom threadpool pointers are validated and initialized correctly.
- Enhanced error handling in parallel_for method to catch exceptions and rethrow them appropriately.
- Modified IDFilterAdapter to check for null operations and validate filter rates during initialization.
- Adjusted test cases to reflect changes in function signatures and ensure proper error handling.
- Introduced new utility functions for initializing search results and memory breakdown structures.
- Updated sequential threadpool implementation to return a boolean indicating success.
@mergify

mergify Bot commented Aug 12, 2026

Copy link
Copy Markdown

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@rfsaliev

Copy link
Copy Markdown
Member Author

@copilot, write a comment with key changes in details and instructions how to convert client code for new API

Copilot AI commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

@copilot, write a comment with key changes in details and instructions how to convert client code for new API

Key Changes & Migration Guide for the New C API

This PR introduces breaking changes to the C API. Below is a summary of every significant change and how to update your client code.


1. Header Path: svs/c_api/svs/c/

All public headers have moved from include/svs/c_api/ to include/svs/c/.

Before:

#include "svs/c_api/svs_c.h"
#include "svs/c_api/svs_c_config.h"

After:

#include "svs/c/svs_c.h"
#include "svs/c/svs_c_config.h"

2. New Version Header

A generated svs/c/svs_c_version.h is now available (automatically included via svs_c.h). It provides:

SVS_C_API_VERSION_MAJOR   // e.g. 0
SVS_C_API_VERSION_MINOR   // e.g. 4
SVS_C_API_VERSION_PATCH   // e.g. 0
SVS_C_API_VERSION_STRING  // e.g. "0.4.0"
SVS_C_API_VERSION         // packed integer: (major<<16)|(minor<<8)|patch

3. Search Results: opaque pointer → value type with accessor

svs_search_results_t is no longer an opaque heap-allocated pointer — it is now a value-type struct initialized with a macro.

Before:

svs_search_results_t results = NULL;

results = svs_index_search_topK(index, queries, num_q, K,
                                 search_params, NULL, error);
if (!results) { /* error */ }

size_t offset = 0;
for (size_t q = 0; q < results->num_queries; q++) {
    for (size_t i = 0; i < results->results_per_query[q]; i++) {
        printf("id=%zu dist=%.4f\n",
               results->indices[offset + i],
               results->distances[offset + i]);
    }
    offset += results->results_per_query[q];
}
svs_search_results_free(results);

After:

svs_search_results_t results = SVS_INIT_SEARCH_RESULTS();

if (!svs_index_search_topk(index, queries, num_q, K,
                            &results, search_params, NULL, error)) {
    /* error */
}

for (size_t q = 0; q < results.num_queries; q++) {
    const size_t* ids;
    const float*  dists;
    size_t        count;
    svs_search_results_row(&results, q, &ids, &dists, &count);
    for (size_t i = 0; i < count; i++) {
        printf("id=%zu dist=%.4f\n", ids[i], dists[i]);
    }
}
svs_search_results_free(&results);  // now takes a pointer

Key changes:

  • Initialize with SVS_INIT_SEARCH_RESULTS() instead of NULL.
  • Pass &results as an output parameter to svs_index_search_topk (lowercase k, note the rename too).
  • Access per-query rows via svs_search_results_row(&results, q, &ids, &dists, &count).
  • svs_search_results_free() now takes svs_search_results_t* (a pointer to the struct).
  • Optionally supply your own pre-allocated buffers with SVS_INIT_SEARCH_RESULTS_WITH_BUFFERS(...).

4. Thread Pool Interface: manual struct init → macros

The custom thread pool interface now uses versioned structs initialized via convenience macros, and parallel_for has a new signature with an error output parameter.

Before:

size_t my_tp_size(void* self) { return N; }

void my_tp_parallel_for(void* self, void (*func)(void*, size_t),
                         void* param, size_t n) {
    for (size_t i = 0; i < n; i++) func(param, i);
}

static struct svs_threadpool_interface my_tp = {
    { &my_tp_size, &my_tp_parallel_for },
    NULL /* self ptr */,
};

After:

size_t my_tp_size(void* self) { return N; }

bool my_tp_parallel_for(void* self, void (*func)(void*, size_t),
                         void* param, size_t n, svs_error_h out_err) {
    for (size_t i = 0; i < n; i++) func(param, i);
    return true;   // return false and populate out_err on failure
}

static svs_threadpool_ops_t my_tp_ops =
    SVS_INIT_THREADPOOL_OPS(my_tp_size, my_tp_parallel_for);

static svs_threadpool_t my_tp = SVS_MAKE_INTERFACE(NULL /* self ptr */, my_tp_ops);

Key changes:

  • Use svs_threadpool_ops_t / svs_threadpool_t typedefs.
  • Initialize ops with SVS_INIT_THREADPOOL_OPS(size_fn, parallel_for_fn).
  • Initialize the interface handle with SVS_MAKE_INTERFACE(self_ptr, ops).
  • parallel_for now returns bool and receives an extra svs_error_h out_err parameter.

5. svs_index_dynamic_add_points: return-value convention change

Before:

size_t n = svs_index_dynamic_add_points(index, data, ids, count, error);
if (n == (size_t)-1) { /* error */ }

After:

size_t n = 0;
if (!svs_index_dynamic_add_points(index, data, ids, count, &n, error)) {
    /* error */
}

6. CMake: updated install layout and include directories

If you consume the library via CMake's find_package, no changes are needed — the exported target now correctly exposes include/svs/c as a public include directory. The installed header directory changed from <prefix>/include/svs/c_api/ to <prefix>/include/svs/c/.

Update any hardcoded -I<prefix>/include/svs/c_api flags to -I<prefix>/include/svs/c (or better, just use the CMake target).


7. Compiler requirement check (new)

The header now emits a compile-time error if the compiler does not support C99 (or C++20 when included from C++). No migration needed — you will get a clear diagnostic if your build falls below the minimum.

@ethanglaser ethanglaser left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall a lot of good changes. Not CMake and versioning are looking good.

Comment on lines +25 to +39
// SVS C API requires a C99 or later compiler, or a C++20 or later compiler. If the
// compiler does not meet these requirements, a compilation error will be generated with a
// clear message indicating the required standard version.
#if defined(__cplusplus)
#if __cplusplus < 202002L
#error \
"svs_c.h requires C++20 or later (designated initializers in SVS_INIT_* / SVS_MAKE_INTERFACE)."
#endif
#elif defined(__STDC_VERSION__)
#if __STDC_VERSION__ < 199901L
#error \
"svs_c.h requires C99 or later (designated initializers in SVS_INIT_* / SVS_MAKE_INTERFACE)."
#endif
#else
#error "svs_c.h requires C99 or later, or C++20 or later."

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get that this helps ensure compiler version use aligns with requirements, but we don't have these types of checks elsewhere afaik and are we really expecting people to be trying to build this with compilers outside of these scopes? Maybe worth a short note in docs instead

Edit: looks like we also have this enforce in CMakeLists.txt as well, so maybe we stick with that

)

# Include directories
target_include_directories(${TARGET_NAME} PRIVATE

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this same block be removed from samples CMakeLists.txt?

selectivity; if the observed hit rate ends up lower than the reported estimate the
function returns an empty result set for that query.

## API Reference

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth having 400+ lines of API reference here along with the verbosity of comments in svs_c.h? Maybe better to have one reference the other to avoid having to update both moving forward

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a different setup than the runtime bindings, though seems like the right approach. Follow-up work can include a similar update to the runtime bindings.


/// @brief Structure to hold memory breakdown for an index.
///
/// Forward-compatibility contract: On any write to this OUT struct, the library

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment block as line 262

Comment thread bindings/c/CMakeLists.txt
# C++20 is required to build this library, but not to consume it: the public
# surface is a C ABI. Keep the requirement PRIVATE so that pure-C consumers are
# not forced to compile as C++20.
target_compile_features(${TARGET_NAME} PRIVATE cxx_std_20)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth adding target_compile_features(${TARGET_NAME} INTERFACE c_std_99) as well?

Comment thread bindings/c/README.md
Comment on lines +44 to +48
Configure and build from the top of the ScalableVectorSearch tree; the C API is
picked up as a subdirectory under `bindings/c`:

```bash
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure it can be built from top of tree like comment suggests - there is no target in root CMakeLists.txt. Something like cmake -S bindings/c -B build -DCMAKE_BUILD_TYPE=Release would work

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants