ABI Stability Guarantees

View as Markdown

Overview

ABI stands for Application Binary Interface. It defines how compiled programs interact with a shared library at runtime.

For cuVS, ABI stability means that an application built against one supported version of the cuVS C library can run with a compatible newer cuVS runtime without needing to be rebuilt.

For example:

Application built against: cuVS 26.04
Runtime installed by user: cuVS 26.06
Result: works, as long as both versions use the same ABI major version

ABI stability is different from API stability:

ConceptMeaning
API stabilitySource code continues to compile.
ABI stabilityAlready-compiled binaries continue to run.

A source-compatible change may still break ABI if it changes the binary layout, symbol names, function signatures, or expected runtime behavior.


Why ABI Stability Is Needed

cuVS is used by downstream projects and language bindings such as Java, Rust, Go, and database integrations.

These users often want to build their application once and allow the final runtime environment to provide cuVS separately. Without ABI stability, downstream consumers may need to rebuild or repackage every time cuVS changes.

ABI stability enables this model:

Vendor application
built once against a supported cuVS version
User environment
provides a compatible cuVS shared library
Result
the application can load and run without being rebuilt

This is especially important for database vendors and other software providers that do not want to vendor or bundle a private copy of cuVS with every release.

ABI stability helps provide:

  • Predictable runtime compatibility
  • Smaller downstream packages
  • Easier system-level deployment
  • Safer upgrades for users
  • Clear rules for when breaking changes are allowed

ABI Compatibility Rule

A cuVS runtime is ABI-compatible with an application when both of the following are true:

  1. The runtime has the same ABI major version as the version used at build time.
  2. The runtime cuVS version is the same or newer than the version used at build time.

Example compatibility matrix:

Built WithRuntimeCompatible?Reason
cuVS 26.04, ABI 1.1cuVS 26.06, ABI 1.2YesSame ABI major, newer runtime
cuVS 26.04, ABI 1.1cuVS 26.02, ABI 1.0NoRuntime is older
cuVS 26.04, ABI 1.1cuVS 26.08, ABI 2.0NoABI major changed

Shared Library Naming

The cuVS C shared library follows this pattern:

libcuvs_c.so.<abi-major>.<abi-minor>

For example:

libcuvs_c.so.1.2

Where:

1 = ABI major version
2 = ABI minor version

The SONAME uses only the ABI major version:

libcuvs_c.so.1

Applications that dynamically load cuVS should load the ABI-major versioned name:

libcuvs_c.so.1

They should not load the fully specified file name:

libcuvs_c.so.1.2

Loading the ABI-major versioned name allows compatible ABI-minor updates to work without relinking or rebuilding the application.


Scope of the ABI Guarantee

The ABI stability guarantee applies to the public C ABI.

It applies to public C interface items that meet these conditions:

  • They are installed under include/cuvs/
  • They are declared in .h header files
  • They are inside an extern "C" block
  • They are part of the public cuVS C interface

The guarantee covers public C functions, enums, and structs that are exposed through the stable C interface.

It does not generally apply to internal implementation details or to the general C++ implementation ABI.


Struct Stability

Structs require special care.

A struct is only ABI-stable when it is allocated, initialized, or managed by ABI-stable cuVS functions. User code should not directly depend on the internal layout of cuVS structs.

Recommended pattern:

1cuvsHnswIndex_t index;
2cuvsHnswIndexCreate(&index);
3
4cuvsHnswIndexParams_t params;
5cuvsHnswIndexParamsCreate(&params);
6
7cuvsHnswFromCagra(res, params, cagra_index, index);

Avoid this pattern:

1cuvsHnswIndex index = { ... };
2cuvsHnswIndexParams params = { ... };
3
4cuvsHnswFromCagra(res, &params, cagra_index, &index);

The second example is risky because it depends on struct layout. If cuVS later adds, removes, reorders, or changes fields, compiled applications may break.


Developer Guide: How to Avoid Breaking ABI

General Rule

When changing public C headers, assume that existing applications may already be compiled against the current ABI.

Use this rule of thumb:

Adding a new symbol is usually safe.
Changing an existing symbol is usually unsafe.
Removing an existing symbol is an ABI break.

Safe Changes in ABI-Compatible Releases

The following changes are generally safe during ABI-compatible releases:

  • Add a new public C function
  • Add a new enum value when it does not change existing values or behavior
  • Add new functionality behind a new symbol
  • Add fields to structs that are fully allocated and managed by cuVS
  • Add new optional behavior without changing existing function signatures
  • Add new APIs while leaving old APIs intact

Example of a safe additive change:

1cuvsStatus_t cuvsFoo(cuvsHandle_t handle, int n);
2
3/* New symbol added later */
4cuvsStatus_t cuvsFooWithOptions(
5 cuvsHandle_t handle,
6 int n,
7 cuvsFooOptions_t options
8);

The original function remains unchanged, so existing binaries continue to work.


Unsafe Changes in ABI-Compatible Releases

Do not make these changes in ABI-compatible releases:

  • Remove a public function
  • Rename a public function
  • Change a function return type
  • Add, remove, or reorder function arguments
  • Change the type of a function argument
  • Change the size or layout of a public struct that users may construct directly
  • Remove or rename struct fields
  • Change the type of a struct field
  • Remove or renumber enum values
  • Change the meaning of an existing enum value
  • Change behavior in a way that violates existing runtime expectations

Example of an ABI break:

1/* Original */
2cuvsStatus_t cuvsFoo(cuvsHandle_t handle, int n);
3
4/* ABI-breaking change */
5cuvsStatus_t cuvsFoo(cuvsHandle_t handle, int64_t n);

Even though the function name is the same, the binary signature changed.


How to Make an Incompatible Change Safely

If a function needs an incompatible signature, do not change the existing function directly.

Instead, add a new suffixed function.

Example:

1/* Existing ABI-stable function */
2cuvsStatus_t cuvsFoo(cuvsHandle_t handle, int n);
3
4/* New replacement function */
5cuvsStatus_t cuvsFoo_v1(
6 cuvsHandle_t handle,
7 int64_t n,
8 float threshold
9);

The old function remains available for existing binaries. New applications can use the new suffixed function.

The old function should be documented as deprecated or superseded, and release notes should explain which replacement should be used.


Consolidating During ABI-Breaking Releases

cuVS has planned releases where ABI-breaking changes are allowed. These are the releases where accumulated compatibility work can be consolidated.

During normal ABI-compatible releases, developers may accumulate suffixed replacement APIs:

1cuvsStatus_t cuvsFoo(...);
2cuvsStatus_t cuvsFoo_v1(...);
3cuvsStatus_t cuvsFoo_v5(...);

During an ABI-breaking release, the highest replacement can become the canonical unsuffixed API:

1cuvsStatus_t cuvsFoo(...);

For example:

ABI 1.x:
cuvsFoo()
cuvsFoo_v1()
cuvsFoo_v5()
ABI 2.0:
cuvsFoo() now uses the cuvsFoo_v5 signature

At that point, older variants can be removed because the ABI major version has changed.


ABI-Breaking Release Checklist

When performing ABI consolidation during a planned ABI-breaking release:

  • Bump the ABI major version
  • Update the SONAME
  • Remove obsolete symbols that are no longer supported
  • Promote the latest suffixed replacement to the canonical unsuffixed API
  • Update public headers
  • Update documentation
  • Update release notes
  • Update the compatibility matrix
  • Update ABI baseline or ABI checking data
  • Ensure packaging uses the correct shared library version
  • Clearly document old signatures and their replacements

Example transition:

Before:
libcuvs_c.so.1
cuvsFoo()
cuvsFoo_v1()
cuvsFoo_v5()
After:
libcuvs_c.so.2
cuvsFoo() // uses the cuvsFoo_v5 behavior/signature

Practical Checklist for Developers

Before modifying a public C header, ask:

  • Is this header installed under include/cuvs/?
  • Is this declaration inside extern "C"?
  • Could downstream code already be compiled against this symbol?
  • Am I changing a function name, return type, or parameter list?
  • Am I changing an enum value or its meaning?
  • Am I changing a struct layout that user code may depend on?
  • Would an already-compiled application still load and call this correctly?
  • Should this be a new suffixed symbol instead of a direct change?

Use this decision guide:

ChangeAllowed in ABI-compatible release?Recommended approach
Add a new functionYesAdd a new symbol
Change a function signatureNoAdd a suffixed replacement
Remove a functionNoWait for ABI-breaking release
Rename a functionNoAdd new function, keep old one
Add managed struct fieldsUsuallyOnly if users cannot construct the struct directly
Change public struct layoutRiskyAvoid, or wait for ABI-breaking release
Remove enum valueNoWait for ABI-breaking release

Summary

ABI stability lets applications built against one cuVS C library version run with compatible newer cuVS runtimes without being rebuilt.

Developers should preserve existing public C ABI symbols during ABI-compatible releases. Add new symbols instead of changing existing ones. When an incompatible change is required, introduce a suffixed replacement such as _v1, keep the old symbol available, and document the migration path.

During planned ABI-breaking releases, developers can consolidate these suffixed replacements by promoting the newest version to the canonical unsuffixed API, removing obsolete variants, and incrementing the ABI major version.