Zig · Inputs and Outputs

Inputs and Outputs

const usearch = @import("usearch");const Scalar = @import("usearch").Scalar;const Traversal = @import("usearch").Traversal;const RowsView = @import("usearch").RowsView;const Matches = @import("usearch").Matches;const Arena = @import("usearch").Arena; 3.0.0-rc.1 · 4c3e5023

§ Arenas

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.

§ Rows & Scalars

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.

API

Types

QueryMatchesThe matches of one query: nearest first for a search, largest first for a maxsim, and in traversal order for a multiply.
pub const QueryMatches = struct

Binding-only.

Fields

keys[]const KeyThe keys written for the query.
scores[]const DistancePositionally paired with keys: distances for a search, similarities for the linear-algebra verbs.
DenseTraversalStatsTraversal telemetry, fully overwritten by the call it is passed to.
pub const DenseTraversalStats = extern struct

Mirrors usearch_traversal_stats_t.

Fields

visited_membersusizeGraph members visited while resolving the batch.
computed_distancesusizeDistances computed while resolving the batch, diversity re-selections included.
restartsusizeStarvation restarts - a graph-connectivity health signal rather than a cost counter.
missing_hintsusizeHint keys no shard held an entry under; zero unless a search was handed hints.
DenseInsertConfigOptional settings of DenseIndex.insert.
pub const DenseInsertConfig = struct

Fields

expansion_factorusizeThe breadth of the candidates the build walk explores; zero derives.
progressProgressCallbackCalled with entries admitted; it reports and cannot cancel, since every entry is claimed first.
progress_stepsusizeReports the call makes at most, the last at completion; zero derives to ten.
stats?*DenseTraversalStats
DenseSearchConfigOptional settings of DenseIndex.search.
pub const DenseSearchConfig = struct

Fields

expansion_factorusizeThe breadth of the candidates the walk explores; zero derives.
traversalTraversalA walk, or an exact scan of every entry on RAM and NVMe handles; VRAM answers Error.Unsupported.
predicatePredicateAdmits only the keys it approves; non-matching entries still route the walk. Refused on VRAM.
progressProgressCallbackCalled with queries resolved; a stopped search answers Error.Cancelled and no matches.
progress_stepsusizeReports 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

Fields

keys[]const Key
vectorsRowsView
configDenseInsertConfig
outcomeOutcome
DenseSearchOne DenseIndex.search, as a Deferred holds it.
pub const DenseSearch = struct

Fields

queriesRowsView
wantedusize
configDenseSearchConfig
stampArena.StampThe call the borrowed arena serves, which every view over the outputs carries.
matchesusearch_matches_view_t
outcomeOutcome
EnumScalarScalar types the indexes store and exchange, named by width and encoding.12
types.zig:262 ↗
pub 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.

Values

unknown0No scalar named - an unset field, or a custom metric owning its own storage.
f641IEEE-754 binary64.
f322IEEE-754 binary32.
bf163Truncated f32 - 8-bit exponent, 7-bit mantissa.
f164IEEE-754 binary16.
e5m25FP8 with a 5-bit exponent.
e4m36FP8 with a 4-bit exponent.
e3m27FP6 with a 3-bit exponent, byte-aligned with two padding bits.
e2m38FP6 with a 2-bit exponent, byte-aligned with two padding bits.
i8108-bit signed integer.
i4x211Two signed nibbles per byte, even dimensions low.
i321232-bit signed integer - the coordinates of geospatial points.
u8208-bit unsigned integer.
u4x221Two unsigned nibbles per byte, even dimensions low.
u1x822Eight 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.
types.zig:299 ↗
pub const BF16 = extern struct

Fields

bitsu16
E5M2The raw bits of one .e5m2 value.
types.zig:301 ↗
pub const E5M2 = extern struct

Fields

bitsu8
E4M3The raw bits of one .e4m3 value.
types.zig:303 ↗
pub const E4M3 = extern struct

Fields

bitsu8
E3M2The raw bits of one .e3m2 value, two padding bits high.
types.zig:305 ↗
pub const E3M2 = extern struct

Fields

bitsu8
E2M3The raw bits of one .e2m3 value, two padding bits high.
types.zig:307 ↗
pub const E2M3 = extern struct

Fields

bitsu8
I4x2Two signed nibbles of .i4x2, the even dimension low.
types.zig:309 ↗
pub const I4x2 = extern struct

Fields

bitsu8
U4x2Two unsigned nibbles of .u4x2, the even dimension low.
types.zig:311 ↗
pub const U4x2 = extern struct

Fields

bitsu8
U1x8Eight single-bit dimensions of .u1x8, the first one lowest.
types.zig:313 ↗
pub const U1x8 = extern struct

Fields

bitsu8
ofThe encoding a Zig element type stores as: a primitive, or one of the raw-bit types above.
types.zig:316 ↗
pub fn of(comptime Element: type) Scalar
dimensionsPerValueLogical dimensions one stored value carries: more than one for the packed kinds.
types.zig:337 ↗
pub fn dimensionsPerValue(self: Scalar) usize

Binding-only.

nameThe canonical spelling, as .f32 spells "f32".
types.zig:346 ↗
pub fn name(self: Scalar) [:0]const u8

Calls usearch_scalar_name.

fromNameReads a spelling back, or null when no kind spells it.
types.zig:351 ↗
pub fn fromName(text: []const u8) ?Scalar

Calls usearch_scalar_from_name.

EnumTraversalHow a search reaches the indexed entries it scores.2
types.zig:378 ↗
pub const Traversal = enum(c_int)

Mirrors usearch_traversal_t.

Values

walk1Walk the graph, scoring the entries the expansion reaches - approximate, the default.
scan2Score every entry published when the query's scan starts - exact, on RAM and NVMe handles.
nameThe canonical spelling, as .scan spells "scan".
types.zig:385 ↗
pub fn name(self: Traversal) [:0]const u8

Calls usearch_traversal_name.

fromNameReads a spelling back, or null when no traversal spells it.
types.zig:390 ↗
pub fn fromName(text: []const u8) ?Traversal

Calls usearch_traversal_from_name.

StructRowsViewA 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.2
types.zig:444 ↗
pub 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.

Fields

data?*const anyopaqueBase pointer of the first row.
rowsusizeRows the run carries.
columnsusizeLogical scalars per row, as the caller counts them - dimensions, never bytes.
strideusizeBytes between the starts of consecutive rows; zero repeats the first row.
scalarScalarThe 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.
types.zig:462 ↗
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.
types.zig:474 ↗
pub fn ofRow(
    comptime Element: type,
    data: []const Element,
) Error!RowsView
StructMatchesThe 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.2
types.zig:526 ↗
pub 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.

Fields

stampArena.Stamp
viewusearch_matches_view_t
queriesusizeQueries 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.
types.zig:534 ↗
pub fn query(self: Matches, position: usize) Error!QueryMatches
residencyWhere the matches live, which the host reads only as .ram or .nvme.
types.zig:543 ↗
pub fn residency(self: Matches) Residency
StructArenaMemory the library writes every output whose length scales with a call into - matches, rows, products, monoliths and cursor batches - mirroring usearch_arena_t.4
types.zig:986 ↗
pub 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.

Fields

handle?*anyopaqueThe opaque usearch_arena_t the ABI hands back; null once freed.
residencyResidencyThe residency of the executor that made it: host pages, or a GPU's memory as .vram.
generationusizeCalls 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.
types.zig:997 ↗
pub fn init(executor: ?Executor) Error!Arena

Calls usearch_arena_init.

deinitFrees every output in it, waiting for every deferred call naming it, and leaves every view over it refusing reads.
types.zig:1013 ↗
pub fn deinit(self: *Arena) void

Calls usearch_arena_free.

StampThe arena and call a view was written by.
types.zig:1020 ↗
pub const Stamp = struct

Binding-only.

Fields

arena*const Arena
generationusize
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.
types.zig:1026 ↗
pub fn check(self: Stamp) Error!void