pub const QueryMatches = struct Binding-only.
Every output whose length only the call knows, such as matches, rows, products, monoliths and cursor or stream batches, lands in an Arena you pass, and the verb answers a view over it without copying. Arena.init(executor) places it where that executor computes, decided once, and Arena.init(null) makes host pages for the calling thread.
Each call rewinds its arena and grows it only past its high-water mark, so the previous call’s views die and a steady loop allocates once.
To keep a result past the next call, or to read one while making the next, pass that call another arena.
An arena serves one call at a time, so each deferred call needs its own.
Views find their arena by address, so it must not move while they live, and a stale view answers Error.Uninitialized; slices an accessor already returned are not checked again.
An arena from a VRAM executor holds device memory, which host accessors refuse with Error.Device.
It grows and frees on that executor’s stream, so it serves only calls on that executor, any other answering Error.Device, and is freed before it; host pages may outlive theirs.
var arena = try usearch.Arena.init(executor); // `Arena.init(null)` is host pages for the calling thread
defer arena.deinit();
for (batches) |batch| {
const matches = try index.on(executor).search(batch, 10, &arena, .{}); // rewinds: the last batch's matches are gone
total += (try matches.query(0)).scores[0];
}
var found = try usearch.Arena.init(null); // two live results, two arenas
defer found.deinit();
var fetched = try usearch.Arena.init(null);
defer fetched.deinit();
const nearest = try index.search(query, 10, &found, .{});
for ((try nearest.query(0)).keys) |key| rerank(key, try index.get(key, .f32, &fetched));
var calls: [shards.len]usearch.Deferred(usearch.DenseSearch) = undefined;
for (&calls, shards, executors, &arenas) |*call, shard, pool, *own|
try shard.deferred(pool, call).search(queries, 10, own, .{}); // one executor and one arena per submitted call
defer for (&calls) |*call| call.deinit();
for (&calls) |*call| consume(try call.wait());A call that finds its arena serving another call finishes with Error.Contended and rewinds nothing, so the holder’s views stay readable.
A call that fails for any other reason still rewinds it.
Only fixed-size reports like DenseTraversalStats stay your fields.
Every batch input is a run of rows given by a base pointer, a row count, a column count, and a byte stride, so a memory-mapped dataset feeds the index with no reshape and no copy.
The scalar type of the input is independent of the stored quantization, so the f32 slab of the Quickstart lands in an i8 index and nothing needs converting before insertion. RowsView.of reads a packed slab; a dataset whose rows are padded to a wider pitch fills the struct directly, setting stride to the pitch; a zero stride repeats the first row for every one of them.
The encodings Zig has no primitive for, usearch.Scalar.BF16, E5M2, E4M3, E3M2, E2M3, and the packed I4x2, U4x2 and U1x8, travel as their raw bits, and a packed kind counts columns in dimensions, so 128 dimensions of Scalar.U1x8 take sixteen values a row.
Lengths that cannot be read as a whole number of rows are refused with Error.ShapeMismatch rather than asserted, so the check survives ReleaseFast.
The column count travels with the rows, and the engine refuses one that differs from the frozen geometry with the same error.
Every metric derives from a single inner-product kernel with pre-computed magnitudes.
.inner: inner product, and the only kind the linear-algebra verbs accept..angular: cosine distance, over pre-computed magnitudes..euclidean: L2 distance..hamming: bit-level, over .u1x8 vectors alone..jaccard: bit-level, over .u1x8 vectors alone.Arbitrary metrics belong to the C++ baseline tier; the C interface, and so this module, takes the built-in kinds alone.
Every verb has one entry point, its optional knobs riding one config struct named after its C argument structure, such as DenseSearchConfig, DenseInsertConfig or DenseMultiplyConfig.
var arena = try usearch.Arena.init(executor);
defer arena.deinit();
var stats: usearch.DenseTraversalStats = .{};
const matches = try index.on(executor).search(try .of(f32, queries, 768), 10, &arena, .{
.expansion_factor = 128,
.stats = &stats,
});
for (0..matches.queries) |query| consume(try matches.query(query));
std.debug.print("{d} distances computed\n", .{stats.computed_distances});Matches.query answers one query’s keys and distances, nearest first, as many as the walk found up to the “k” asked for.
The breadth of the candidates rides the call rather than the handle, so a recall sweep is a loop over expansion factors against one immutable index, with no mutation and no synchronization between points. .traversal = .scan scores every entry instead of walking, on RAM and NVMe handles, which gives that sweep its exact ground truth; a VRAM handle answers it with Error.Unsupported.
QueryMatchesThe matches of one query: nearest first for a search, largest first for a maxsim, and in traversal order for a multiply.DenseTraversalStatsTraversal telemetry, fully overwritten by the call it is passed to.pub const DenseTraversalStats = extern struct Mirrors usearch_traversal_stats_t.
visited_members | usize | Graph members visited while resolving the batch. |
|---|---|---|
computed_distances | usize | Distances computed while resolving the batch, diversity re-selections included. |
restarts | usize | Starvation restarts - a graph-connectivity health signal rather than a cost counter. |
missing_hints | usize | Hint keys no shard held an entry under; zero unless a search was handed hints. |
DenseInsertConfigOptional settings of DenseIndex.insert.pub const DenseInsertConfig = struct expansion_factor | usize | The breadth of the candidates the build walk explores; zero derives. |
|---|---|---|
progress | ProgressCallback | Called with entries admitted; it reports and cannot cancel, since every entry is claimed first. |
progress_steps | usize | Reports the call makes at most, the last at completion; zero derives to ten. |
stats | ?*DenseTraversalStats |
DenseSearchConfigOptional settings of DenseIndex.search.pub const DenseSearchConfig = struct expansion_factor | usize | The breadth of the candidates the walk explores; zero derives. |
|---|---|---|
traversal | Traversal | A walk, or an exact scan of every entry on RAM and NVMe handles; VRAM answers Error.Unsupported. |
predicate | Predicate | Admits only the keys it approves; non-matching entries still route the walk. Refused on VRAM. |
progress | ProgressCallback | Called with queries resolved; a stopped search answers Error.Cancelled and no matches. |
progress_steps | usize | Reports the call makes at most, the last at completion; zero derives to ten. |
stats | ?*DenseTraversalStats |
DenseInsertOne DenseIndex.insert, as a Deferred holds it.pub const DenseInsert = struct keys | []const Key | |
|---|---|---|
vectors | RowsView | |
config | DenseInsertConfig | |
outcome | Outcome |
DenseSearchOne DenseIndex.search, as a Deferred holds it.pub const DenseSearch = struct queries | RowsView | |
|---|---|---|
wanted | usize | |
config | DenseSearchConfig | |
stamp | Arena.Stamp | The call the borrowed arena serves, which every view over the outputs carries. |
matches | usearch_matches_view_t | |
outcome | Outcome |
ScalarScalar types the indexes store and exchange, named by width and encoding.12pub const Scalar = enum(c_int) Sub-byte types pack
several dimensions per byte, as their xN suffixes spell.
Non-exhaustive, so an encoding a newer engine reports arrives intact rather than coerced.
unknown | 0 | No scalar named - an unset field, or a custom metric owning its own storage. |
|---|---|---|
f64 | 1 | IEEE-754 binary64. |
f32 | 2 | IEEE-754 binary32. |
bf16 | 3 | Truncated f32 - 8-bit exponent, 7-bit mantissa. |
f16 | 4 | IEEE-754 binary16. |
e5m2 | 5 | FP8 with a 5-bit exponent. |
e4m3 | 6 | FP8 with a 4-bit exponent. |
e3m2 | 7 | FP6 with a 3-bit exponent, byte-aligned with two padding bits. |
e2m3 | 8 | FP6 with a 2-bit exponent, byte-aligned with two padding bits. |
i8 | 10 | 8-bit signed integer. |
i4x2 | 11 | Two signed nibbles per byte, even dimensions low. |
i32 | 12 | 32-bit signed integer - the coordinates of geospatial points. |
u8 | 20 | 8-bit unsigned integer. |
u4x2 | 21 | Two unsigned nibbles per byte, even dimensions low. |
u1x8 | 22 | Eight single-bit dimensions per byte - what Hamming and Jaccard score. |
BF16The raw bits of one .bf16 value, for the encodings Zig has no primitive for.E5M2The raw bits of one .e5m2 value.E4M3The raw bits of one .e4m3 value.E3M2The raw bits of one .e3m2 value, two padding bits high.E2M3The raw bits of one .e2m3 value, two padding bits high.I4x2Two signed nibbles of .i4x2, the even dimension low.U4x2Two unsigned nibbles of .u4x2, the even dimension low.U1x8Eight single-bit dimensions of .u1x8, the first one lowest.ofThe encoding a Zig element type stores as: a primitive, or one of the raw-bit types above.pub fn of(comptime Element: type) Scalar dimensionsPerValueLogical dimensions one stored value carries: more than one for the packed kinds.nameThe canonical spelling, as .f32 spells "f32".fromNameReads a spelling back, or null when no kind spells it.TraversalHow a search reaches the indexed entries it scores.2pub const Traversal = enum(c_int) Mirrors usearch_traversal_t.
walk | 1 | Walk the graph, scoring the entries the expansion reaches - approximate, the default. |
|---|---|---|
scan | 2 | Score every entry published when the query's scan starts - exact, on RAM and NVMe handles. |
nameThe canonical spelling, as .scan spells "scan".fromNameReads a spelling back, or null when no traversal spells it.RowsViewA borrowed run of rows - the batch currency every modality's row inputs take, mirroring usearch_rows_view_t with the row count it leaves to the call.2pub const RowsView = struct One vector is a one-row run, so a caller never packs a special case. rows is the batch axis a
verb passes on as its count, and columns travels with the rows for the engine to check
against the frozen geometry. A zero stride repeats the first row.
data | ?*const anyopaque | Base pointer of the first row. |
|---|---|---|
rows | usize | Rows the run carries. |
columns | usize | Logical scalars per row, as the caller counts them - dimensions, never bytes. |
stride | usize | Bytes between the starts of consecutive rows; zero repeats the first row. |
scalar | Scalar | The scalar type the rows hold, converted on the way in where it differs. |
ofReads a contiguous slab as rows of columns dimensions, which a packed kind counts in dimensions rather than values, so 128 dimensions of Scalar.U1x8 take sixteen values a row.pub fn of(
comptime Element: type,
data: []const Element,
columns: usize,
) Error!RowsView A zero column count, a width that does not fill whole values, or a slab that is not a whole number of rows is a shape the engine would refuse too, so it is refused here rather than divided by.
ofRowReads one contiguous vector as a one-row run; an empty vector is no row at all.pub fn ofRow(
comptime Element: type,
data: []const Element,
) Error!RowsView MatchesThe ranked matches a search or a stream pull wrote into an arena, one list per query, each holding up to the wanted the call asked for.2pub const Matches = struct Mirrors usearch_matches_view_t.
It reads the arena, so every read is refused once that arena serves another call or is freed.
stamp | Arena.Stamp | |
|---|---|---|
view | usearch_matches_view_t | |
queries | usize | Queries the call resolved. |
queryThe matches written for one query, refusing a position past queries with Error.InvalidArgument, a stale view with Error.Uninitialized, and device memory with Error.Device.pub fn query(self: Matches, position: usize) Error!QueryMatches residencyWhere the matches live, which the host reads only as .ram or .nvme.pub fn residency(self: Matches) Residency ArenaMemory the library writes every output whose length scales with a call into - matches, rows, products, monoliths and cursor batches - mirroring usearch_arena_t.4pub const Arena = struct Every verb writing such outputs borrows one as *Arena, blocking or deferred alike. Each call
rewinds it, so the views of the previous call refuse every read afterwards, and refills it,
growing only past its high-water mark, so a steady loop allocates once. An arena serves one call
at a time: a second use while a call holds it fails with Error.Contended and leaves that call's
views alone. A view finds its arena where the call left it, so an arena stays put, never copied,
while views over it live.
A GPU arena grows, fills and frees on the first stream of the executor that made it, so it
serves only calls on that executor, any other or none answering Error.Device, and must be
freed before that executor is.
handle | ?*anyopaque | The opaque usearch_arena_t the ABI hands back; null once freed. |
|---|---|---|
residency | Residency | The residency of the executor that made it: host pages, or a GPU's memory as .vram. |
generation | usize | Calls it has served, which every view compares against its own. Binding-only. |
initMakes an arena where executor computes, decided once, or host pages for calls on the calling thread when null; host pages may outlive the executor, a GPU arena may not.deinitFrees every output in it, waiting for every deferred call naming it, and leaves every view over it refusing reads.StampThe arena and call a view was written by.Stamp.checkRefuses a read once the arena served another call or was freed with Error.Uninitialized, and a read of device memory with Error.Device.pub fn check(self: Stamp) Error!void