Skip to content

Rust: builder-based params, per-module errors#2284

Open
yan-zaretskiy wants to merge 1 commit into
NVIDIA:mainfrom
yan-zaretskiy:rust-builder-pattern
Open

Rust: builder-based params, per-module errors#2284
yan-zaretskiy wants to merge 1 commit into
NVIDIA:mainfrom
yan-zaretskiy:rust-builder-pattern

Conversation

@yan-zaretskiy

Copy link
Copy Markdown
Contributor

Here we further rework the Rust bindings API to be more idiomatic, and also align closer with the underlying C++ namespacing. More specifically:

  • Every param type is built with a validating builder instead of new() + chained set_*. Unset fields keep the C-library defaults; out-of-range values are rejected by build().
// before
let params = IndexParams::new()?.set_graph_degree(64).set_intermediate_graph_degree(128);
// after
let params = IndexParams::builder().graph_degree(64).intermediate_graph_degree(128).build()?;

Thus, by the time the params instance is constructed, it is guaranteed to be valid.

  • Resources stream is set at construction. Instead of a post-construction setter, we provide a custom stream via unsafe { Resources::with_stream(stream) }. The Resources::new() keeps the default stream.
  • Module layout mirrors C++ namespacing. ANN indexes moved under cuvs::neighbors.
  • The crate-wide cuvs::Error/cuvs::Result are removed. Each module returns its own thiserror enum, e.g. CagraError or IvfFlatError. This enables individual index errors to be more granular, instead of creating a single error type encompassing all failure modes.

@yan-zaretskiy yan-zaretskiy self-assigned this Jul 1, 2026
@yan-zaretskiy yan-zaretskiy requested a review from a team as a code owner July 1, 2026 20:47
@yan-zaretskiy yan-zaretskiy added breaking Introduces a breaking change improvement Improves an existing functionality Rust labels Jul 1, 2026
/// Canberra distance.
Canberra,
/// Generalized Minkowski (Lp) distance with exponent `p`.
LpUnexpanded(f32),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This isn't a big deal, but might be worth noting -- the exponent p is not always honored. Over in the C code, this is only moved when metric_arg exists. Might be worth a comment...it's not a regression from the existing C code and I'm not sure how to address it without a major refactor.


pub use index::Index;
pub use params::{
IndexParams, SearchParams, cudaDataType_t, cuvsIvfPqCodebookGen, cuvsIvfPqListLayout,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You probably don't want to export these native cudaDataType_t, cuvsIvfPqCodebookGen, and cuvsIvfPqListLayout types publicly, since everything else is wrapped.

vq_kmeans_trainset_fraction: Option<f64>,
pq_kmeans_trainset_fraction: Option<f64>,
) -> Result<Self, CagraError> {
if let Some(bits) = pq_bits

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This applies to other news in this file, too: Does the C library have a facility to handle validation of these FFI params for you? It might be worth using to avoid duplicating work.

) -> Result<Self, CagraError> {
let params = Self::try_new()?;

let effective_algo = algo.unwrap_or(unsafe { (*params.handle).algo.into() });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Very minor, but prefer .unwrap_or_else(|| unsafe { (*params.handle).algo.into() }) so you only deref in the None case. unwrap_or eagerly evaluates even when algo is Some(_).


impl IndexParams {
/// Returns a new IndexParams
pub fn new() -> Result<IndexParams> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Since this is deleted, the example over in the README Rust API that uses IndexParams::new() won't work any more. For Rust, there's a clever trick you can use to make sure your READMEs always compile:

#[cfg(doctest)]
#[doc = include_str!("../../README.md")]
pub struct ReadmeDocTests;

This picks up the triple-ticks and makes sure they compile (https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html). Rust is the assumed language, but the examples currently all specify c/python/rust/etc -- Rust doctest will ignore non-Rust languages.

There are some other things in examples/rust that use this same pattern that fail to compile. CI can catch this for you by running cargo build --examples

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

Labels

breaking Introduces a breaking change improvement Improves an existing functionality Rust

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants