Skip to content

Dart Binding

Mazhar Ahmed edited this page Aug 28, 2026 · 3 revisions

Dart Binding

A plain dart:ffi wrapper over the C API. Not a Flutter plugin — the same file works in a console app and in Flutter.

cargo build --release
cd bindings/dart
dart pub get
dart test

Use

import 'qql.dart';

final qql = Qql.open('sources', libraryPath: 'target/release/libqql.so');

try {
  for (final record in qql.execute('Q:2:1-5,255')) {
    print(record['ar']);
  }
} on QqlException catch (e) {
  print('${e.code}: ${e.message}');
} finally {
  qql.dispose();
}
Member Does
Qql.open(dataDir, {libraryPath}) opens a context
execute(query) List<Map<String, dynamic>>, throws QqlException
executeJson(query) the raw JSON string; never throws for a bad query
version library version
dispose() releases the native context; idempotent

libraryPath defaults to the platform's usual name — libqql.so, qql.dll, libqql.dylib.

Search

Searching is part of the query string, so there is no extra API:

qql.execute('q:1:"الحمد"');    // Arabic, exact
qql.execute('q:2:"prayer"');   // English, exact
qql.execute("q:1:'الحمد'");    // either quote delimits a term
qql.execute('q:1:?"mercy"');   // ranked full text  (fulltext feature)
qql.execute('q:1:*"worship"');  // ranked similarity (vector feature)

Ranked hits carry score and ranked: true, ordered by score.

Features are compiled into the library

This is the one thing that catches people. The feature set lives in libqql.so, not in Dart. A library built without them answers ?"…" and *"…" with QqlException('QQL_UNSUPPORTED').

cargo build --release --features vector,fulltext
cargo run --features fulltext --bin qql-index    # tantivy indexes

A plain cargo build --release — including the one inside scripts/c-smoke.sh — overwrites the library with a featureless one.

Memory

The binding owns two kinds of native memory and releases both:

Allocation Freed by
Query strings (toNativeUtf8) calloc.free — Dart allocated them
Result strings from Rust qql_free_string — never calloc.free
The context Qql.dispose()

Crossing those wires corrupts the heap. Dart's GC knows nothing about the Rust allocation, so dispose() is not optional — there is no finalizer.

Isolates

One Qql instance must not be used from two isolates at once. Give each its own; contexts are independent and the cache is per-context.

Bundling

Platform Artifact Notes
Android libqql.so per ABI cargo ndk -t arm64-v8a -t armeabi-v7a build --release
iOS libqql.a static — the App Store discourages loose dynamic libraries
Linux / Windows / macOS libqql.so / qql.dll / libqql.dylib

All fall out of the crate-type list in Cargo.toml.

The data must ship too. On Android and iOS copy sources/ into app storage at first launch and pass that path to Qql.open. Budget for it:

Size
Text (Quran + hadith + Hisnul Muslim) see Sources
sources/vectors/ ~28 MB, only with vector
sources/fulltext/ ~22 MB, only with fulltext

Tests

Thirteen tests covering what the Rust suite cannot see from this side: Arabic arriving byte-identical to the data file, query order and dedup surviving the boundary, grouping and sticky sources and B::N numbering crossing intact, search in both languages, ranked hits in descending score order, errors becoming QqlException with code and position, and 200 consecutive executions leaving the context healthy.

Clone this wiki locally