Version: 0.9.1 Last Updated: 2026-06-19
This guide explains the different dictionary backends available in liblevenshtein-rust and how to choose the right one for your use case.
The dictionary data structures live in the libdictenstein crate. liblevenshtein re-exports them (the liblevenshtein::prelude re-exports are deprecated since 0.9.1 for convenience; new code should use libdictenstein::<module>::<Type> directly). The *Char variants are the UTF-8 (char/u32) counterparts of the byte-level (u8) backends.
liblevenshtein uses a trait-based design that allows multiple dictionary implementations to share the same fuzzy-matching interface. Each backend has different trade-offs in terms of:
The taxonomy below groups the available backends by their core data structure and intended workload.
Type: Double-Array Trie with conflict resolution
Characteristics:
\mathcal{O}(1)$ transitions, excellent cache locality)DoubleArrayTrieChar variantWhen to use:
Example:
use liblevenshtein::prelude::*;
let dict = DoubleArrayTrie::from_terms(vec![
"test", "testing", "tested", "tester"
]);
let transducer = Transducer::new(dict, Algorithm::Standard);
for term in transducer.query("tset", 2) {
println!("{}", term);
}
Feature flag: dat-backend (enabled by default)
Type: Character-level Double-Array Trie
Characteristics:
When to use:
Example:
use liblevenshtein::prelude::*;
// Multi-byte UTF-8 characters handled correctly
let dict = DoubleArrayTrieChar::from_terms(vec![
"café", "naïve", "日本語", "emoji😀"
]);
let transducer = Transducer::new(dict, Algorithm::Standard);
for candidate in transducer.query_with_distance("cafe", 1) {
println!("{}: {}", candidate.term, candidate.distance);
}
Trade-offs:
Feature flag: dat-backend (enabled by default)
Type: Trie with structural sharing and interior mutability
Characteristics:
Arc<ArcSwap<…>>)PathMapDictionaryChar variantWhen to use:
Example:
use liblevenshtein::prelude::*;
let dict = PathMapDictionary::from_terms(vec![
"test", "testing"
]);
// Insert new terms at runtime
dict.insert("tested");
dict.insert("tester");
// Remove terms
dict.remove("testing");
let transducer = Transducer::new(dict, Algorithm::Standard);
for term in transducer.query("test", 1) {
println!("{}", term);
}
Thread safety:
Arc<ArcSwap<PathMapState>>) — readers never block on a writerFeature flag: pathmap-backend (optional)
Type: Character-level PathMap with dynamic updates
Characteristics:
When to use:
Feature flag: pathmap-backend (optional)
Type: DAWG with online insert/delete/minimize operations
Characteristics:
LockFreeDawg core)When to use:
Example:
use liblevenshtein::prelude::*;
let dict = DynamicDawg::from_terms(vec!["test"]);
// Online insertion with automatic minimization
dict.insert("testing");
dict.insert("tested");
// Online deletion
dict.remove("test");
println!("Nodes after minimization: {}", dict.node_count());
Feature flag: dawg-backend (optional)
Unicode: Use the DynamicDawgChar variant for correct character-level distances.
Type: 64-bit Dynamic DAWG with wait-free reads and 8-byte (u64) edge labels
Characteristics:
When to use:
u64) edge labelu64-labelled variant of the shared lock-free DAWG coreBoth DynamicDawg and DynamicDawgU64 are fully lock-free for reads — they share the
same LockFreeDawg core (a reader retains one immutable root; writes use path-copying
and a root compare_exchange loop). They differ only in the edge-label width:
DynamicDawg uses a 1-byte u8 label, DynamicDawgU64 a wider u64 label (more label
space at some extra memory). See Thread Safety for the concurrency model.
Feature flag: dawg-backend (optional)
Type: Suffix automaton for infix matching
Characteristics:
When to use:
Example:
use liblevenshtein::prelude::*;
let dict = SuffixAutomaton::from_terms(vec![
"testing", "fastest", "contest"
]);
// Can match substring "test" in any position
let transducer = Transducer::new(dict, Algorithm::Standard);
for term in transducer.query("test", 1) {
println!("{}", term);
}
Feature flag: suffix-automaton-backend (optional)
Type: Symmetric Compact Directed Acyclic Word Graph with bidirectional traversal (Scdawg; ScdawgChar for UTF-8)
Characteristics:
\mathcal{O}(\lvert pattern\rvert)$)k query splitterWhen to use:
\mathcal{O}(\lvert pattern\rvert)$ substring searchExample:
use liblevenshtein::prelude::*;
let scdawg = Scdawg::<()>::from_terms(["cathedral", "category", "catering"]);
// O(|pattern|) substring search
assert!(scdawg.contains_substring("cat"));
assert!(scdawg.contains_substring("thedr"));
// Find all occurrences
let matches = scdawg.find_exact_substring("cat");
assert_eq!(matches.len(), 3); // Found in all three terms
Feature flag: scdawg-backend (optional)
Type: Persistent Adaptive Radix Trie with memory-mapped storage (PersistentARTrie; PersistentARTrieChar for UTF-8)
Characteristics:
When to use:
Example:
use libdictenstein::persistent_artrie::PersistentARTrie;
// Create a new persistent dictionary
let dict = PersistentARTrie::create("words.part")?;
// Insert terms (persisted to disk)
dict.insert("hello", ())?;
dict.insert("world", ())?;
// Query with transducer
let transducer = Transducer::new(&dict, Algorithm::Standard);
for result in transducer.query("helo", 1) {
println!("{}: distance {}", result.term, result.distance);
}
Architecture:
Feature flag: persistent-artrie (optional)
| Backend | Construction | Query | Memory | Updates | Unicode Variant |
|---|---|---|---|---|---|
| DoubleArrayTrie | ●●●○○ Medium | ●●●●● Excellent | ●●●●● Minimal | ✗ No | DoubleArrayTrieChar |
| PathMapDictionary | ●●●●○ Fast | ●●●●○ Very Good | ●●●○○ Moderate | ✅ Yes | PathMapDictionaryChar |
| DynamicDawg | ●●●●○ Fast | ●●●○○ Good | ●●●●○ Low | ✅ Yes (lock-free) | DynamicDawgChar |
| DynamicDawgU64 | ●●●●○ Fast | ●●●○○ Good | ●●●●○ Low | ✅ Yes (lock-free) | — (u64 labels) |
| SuffixAutomaton | ●●●●○ Fast | ●●●○○ Good | ●●●○○ Moderate | ✗ No | SuffixAutomatonChar |
| Scdawg | ●●●○○ Medium | ●●●●● Excellent (substring) | ●●●○○ Moderate | ✗ No | ScdawgChar |
| PersistentARTrie | ●●●●○ Fast | ●●●●● Excellent | Disk-based | ✅ Yes | PersistentARTrieChar |
Query performance relative to DoubleArrayTrie (100K terms, distance 2):
| Backend | Relative Speed | Memory (MB) |
|---|---|---|
| DoubleArrayTrie | 1.0× (baseline) | 8.5 |
| DoubleArrayTrieChar | 0.95× | 11.2 |
| PathMapDictionary | 0.92× | 12.3 |
| PathMapDictionaryChar | 0.87× | 16.8 |
| DynamicDawg | 0.85× | 7.8 |
| DynamicDawgU64 | 0.84× | 9.1 |
| SuffixAutomaton | 0.82× | 10.5 |
| Scdawg | 0.90× (substring: 1.2×) | 14.2 |
| PersistentARTrie | 0.88× | Disk + cache |
Note: All backends benefit from SIMD acceleration (20-64% faster, automatic on x86_64 with AVX2/SSE4.1).
The flowchart below distills the prose decision criteria into a single path from your requirements to a recommended backend.
u8) edge labelsu64 (8-byte) edge labels for wait-free reads\mathcal{O}(\lvert pattern\rvert)$ substring searchEnable backends via Cargo features:
[dependencies]
liblevenshtein = {
git = "https://github.com/vinary-tree/liblevenshtein-rust",
tag = "v0.9.1",
features = [
"dat-backend", # DoubleArrayTrie (default)
"pathmap-backend", # PathMapDictionary
"dawg-backend", # DynamicDawg / DynamicDawgU64
"suffix-automaton-backend", # SuffixAutomaton
"scdawg-backend", # Scdawg
"persistent-artrie" # PersistentARTrie
]
}
You can implement your own dictionary backend by implementing the Dictionary trait (defined in libdictenstein):
use libdictenstein::{Dictionary, DictionaryNode};
pub struct MyCustomDictionary {
// Your implementation
}
impl Dictionary for MyCustomDictionary {
type Node = MyNode;
fn root(&self) -> Self::Node {
// Return root node
}
fn len(&self) -> Option<usize> {
// Return number of terms
}
fn contains(&self, term: &str) -> bool {
// Check if term exists
}
// ... other required methods
}
See Developer Guide for more details on custom backends.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |