Vinary Tree liblevenshtein for Ruby

This gem supports maintained Ruby 3.3 through the latest Ruby release. Queries are one-shot Enumerable objects backed by a native cursor; only a bounded batch is leased and each yielded Match owns its term. A query captures its dictionary revision at construction and can outlive the source dictionary.

Any modular dictionary gem can implement `with_resource { |context, vtable| } to participate in O(1) retained-resource handoff. The libdictenstein gem does so without serialization or an object-format conversion.

Set LIBLEVENSHTEIN_LIBRARY for a source-tree build. Release gems contain the platform shared library under lib/vinary_tree/native/<platform>/; a system installation remains a supported loader fallback.

Support and package contract

Property Contract
Binding Ruby
Languages/runtime Ruby 3.3+
Support tier Tier 2
Distribution RubyGems package liblevenshtein
Native boundary Ruby Fiddle calls the stable C ABI; modular producers yield the two-word resource without serialization.
Canonical facade source bindings/ruby/lib/vinary_tree/liblevenshtein

The support tier controls release gating, not semantic quality: every tier has the same snapshot, ownership, status, and ABI compatibility laws. Consult the binding architecture before implementing a custom provider and the family hub when combining independently packaged projects.

The host-language facade crosses one project ABI and retains a versioned family resource rather than sharing Rust object layouts.

Executable example and verification

The repository's canonical executable example is bindings/ruby/test/test_liblevenshtein.rb. It exercises the same public package a user installs and is run by the binding CI with:

ruby -Ibindings/ruby/lib bindings/ruby/test/test_liblevenshtein.rb

Examples deliberately construct or receive resources through public project packages. They never import private Rust modules, depend on object layout, or reach behind the stable C/resource ABIs.

Public API and data model

The idiomatic facade groups the stable surface into these concepts:

Concept Semantics
Dictionary resource A retained vt.dictionary.v1 capability. Construction and mutation belong to a producer such as libdictenstein.
Transducer Immutable query configuration plus a retained dictionary provider; construction is constant-time with respect to dictionary size.
Query cursor A one-shot traversal over the immutable dictionary revision captured at query start.
Match/batch Owned matches are stable host values; a borrowed batch is valid only inside its documented callback or lease interval.

Ruby strings use explicit encoding rules; byte strings and integer arrays select non-Unicode domains. Empty terms, embedded zero bytes, non-ASCII text, and the full unsigned 64-bit identifier range are represented explicitly; no facade may use a sentinel value that removes a valid input from the domain.

For the exhaustive native function contract—including exact preconditions, returnable statuses, complexity, and thread-safety—use the llev_* C ABI reference. The facade source linked above is the authoritative idiomatic symbol inventory; its exhaustive coverage is governed by bindings/api-surface-map.json and the generated completeness matrix.

Ownership, snapshots, and resource handoff

Prefer block forms and ensure { cursor.close }; finalizers only prevent permanent leaks.

A transducer retains the provider resource, and a query retains the revision visible at query start. Closing the original dictionary or publishing later mutations cannot invalidate that query. Acquisition either completes with one owned retain or fails with no ownership transfer. Teardown order is therefore free across dictionary, transducer, and completed query handles.

Borrowed results are intentionally lexical. Copy data that must outlive the callback; retaining a raw address, slice, memory segment, or foreign pointer is an API violation even when the next operation happens to reuse the same arena.

Errors and failure containment

Non-OK statuses become typed Ruby exceptions exposing the status symbol and native diagnostic.

Malformed utf-8, unsupported unit domains, incompatible resource versions, closed handles, invalid bounds, allocation failures, provider faults, and contained rust panics are distinct failures. Never parse diagnostic prose to branch on an error: inspect the typed status/exception first and treat the message as human context. Diagnostics must be copied before another native call on the same thread.

Concurrency and reentrancy

Separate native handles are reentrant. Enumerate one cursor on one fiber/thread and never retain callback-scoped buffers.

Snapshot capture is a linearization point, not a dictionary-wide query lock. First-party immutable snapshots can be walked concurrently. A foreign provider that does not advertise parallel callbacks is serialized at its callback gate; the host language must not add a weaker promise.

Performance and marshalling

  • Reuse transducers for repeated queries against the same resource.
  • Prefer streaming cursors to whole-result materialization.
  • Prefer batch/reducer APIs when per-match boundary crossings dominate.
  • Keep Unicode, byte, and token domains explicit to avoid transcoding.
  • Measure native, WASM, and WASI paths independently; they have different startup and marshalling costs but identical query semantics.

No host wrapper should cache unbounded query results. Applications that add a memo use a revision key and a hard entry/weight bound; eviction may be approximate because all values remain derivable from the retained snapshot.

Security model

Treat a foreign resource provider and all user-controlled queries as untrusted inputs. Validate lengths before allocation, preserve paging bounds, reject unknown enum values, contain callbacks/panics at the boundary, and never trust capability flags until interface negotiation succeeds. The normative duties are in the binding trust model.

Compatibility and troubleshooting

The project ABI revision, family ABI version, interface identity/version, package version, and umbrella-runtime version are independent counters. Follow the ABI evolution policy; never infer compatibility from a package version alone.

When loading fails, check—in order—the documented runtime/toolchain version, CPU/OS artifact, native-access permission, loader search path, dependent interop package pin, and process-wide JavaScript runtime identity. When a query fails after construction, report the typed status and copied diagnostic before reducing the case to the smallest dictionary/query pair.

Maintainer checklist

  1. Update the machine-readable binding model before changing a public symbol.
  2. Regenerate headers/constants and the API coverage matrix.
  3. Extend the canonical executable example and negative-path tests.
  4. Run the language package, snapshot, leak, property, and cross-project suites.
  5. Verify package staging contains this guide and uses coherent sibling pins.
  6. Render diagrams headlessly and run the documentation/link/math gates.