Zig · Filtered Search

Filtered Search

const usearch = @import("usearch");const ProgressCallback = @import("usearch").ProgressCallback;const Predicate = @import("usearch").Predicate; 3.0.0-rc.1 · 4c3e5023

A Predicate in the search config admits only the keys it approves, and non-matching entries still route the traversal, so recall does not collapse at low selectivity. Predicate.of takes a comptime function rather than a closure, so the trampoline that crosses the boundary is generated per call site and nothing is boxed. Its context travels as one pointer: pass {} for a predicate needing none, and a single-item pointer otherwise.

const admits = struct {
    fn call(_: void, key: usearch.Key) usearch.Admission {
        const value = key.asInteger() orelse return .reject;
        return if (value % 2 == 0) .admit else .reject;
}
}.call;

const even = try index.on(executor).search(try .of(f32, queries, 768), 10, &arena, .{
    .predicate = .of({}, admits),
});

It runs once per scored candidate, far more often than the “k” asked for, and concurrently from the executor’s threads, so it belongs on your fast path and must tolerate that. A VRAM index refuses a predicate with Error.Device, since a host callback cannot narrow a device walk.

Every long verb, namely insert, refine, search, absorb, multiply, attend, maxsim, save, saveBuffer and every load, takes a ProgressCallback in its config, called with the units finished so far and the units the call finishes if it runs to the end. Answering .cancel stops the call at the next increment boundary, except an insert, which ignores it.

const Deadline = struct {
    io: std.Io,
    stop_at: i64,
    fn reached(self: *const @This(), _: usize, _: usize) usearch.Continuation {
        const now = std.Io.Clock.awake.now(self.io).toMilliseconds();
        return if (now < self.stop_at) .@"continue" else .cancel;
}
};

const deadline: Deadline = .{ .io = io, .stop_at = std.Io.Clock.awake.now(io).toMilliseconds() + 30_000 };
_ = try index.on(executor).refine(.{
    .expansion_factor = 128,
    .progress = .of(&deadline, Deadline.reached),
});

Like a predicate, the handler is a comptime function whose trampoline is generated per call site, and its context travels as one pointer, {} where none is needed. It arrives on whichever thread advanced the work and never on two at once, so a counter or a deadline needs no synchronization of its own. Reports are denominated in whatever the verb consumes, such as entries for an insertion, queries for a search, donors for an absorb and shards for a checkpoint, and how often they arrive is the increment’s size rather than a guarantee, so read the counts and never the number of calls.

An insert cannot be stopped: it claims every entry before its first report and publishes the whole batch at once, so a batch is all-or-nothing with or without a handler. A stopped refine is not a failure but the time budget a whole-graph sweep is meant to run under: re-selection claims no identity, so the graph stays correct and merely less improved, and the next call sweeps again, so calling under a time budget still converges.

API

Types

AdmissionWhether a filtered search admits one candidate key or passes over it.
pub const Admission = enum(c_int)

Mirrors usearch_admission_t.

Values

reject0Skip the key; the walk still routes through it.
admit1Report the key among the matches.
ContinuationWhether the operation a handler is watching carries on or unwinds.
pub const Continuation = enum(c_int)

Mirrors usearch_continuation_t.

Values

@"continue"0Carry on to the next unit of work.
cancel1Stop; what the finished increments leave behind is each verb's own contract.
StructProgressCallbackA progress handler as the C interface carries it: a usearch_progress_callback_t and the state forwarded into it.1
types.zig:626 ↗
pub const ProgressCallback = struct

Build one with ProgressCallback.of; the default reports nowhere.

Fields

callback?ProgressFunction
state?*anyopaque
ofWraps reports in a trampoline generated per call site, so it reads a typed context rather than an anyopaque.
types.zig:636 ↗
pub fn of(
    context: anytype,
    comptime reports: fn (@TypeOf(context), usize, usize) Continuation,
) ProgressCallback

Pass {} for a handler needing no context, and a single-item pointer otherwise.

The handler arrives on whichever thread advanced the work and never on two at once, so it needs no synchronization of its own; answering .cancel stops the call.

StructPredicateWhich candidate keys a filtered search may report: a handler and the state forwarded into it.1
types.zig:695 ↗
pub const Predicate = extern struct

Build one with Predicate.of; the default admits everything. Mirrors usearch_predicate_t.

Fields

callback?PredicateFunctionThe optional handler; null admits every key.
state?*anyopaqueOpaque pointer forwarded into every callback call.
costPredicateCostHow many keys each callback call carries.
ofWraps admits in a trampoline generated per call site, asking it about each key of a batch.
types.zig:708 ↗
pub fn of(
    context: anytype,
    comptime admits: fn (@TypeOf(context), Key) Admission,
) Predicate

Pass {} for a predicate needing no context, and a single-item pointer otherwise.

It judges every scored candidate, far more than wanted, and runs concurrently from the executor's threads - so it belongs on the caller's fast path and must tolerate that.