Streaming Replace
replace_stream is the streaming counterpart of replace: it reads
from a Read, writes the transformed stream to a Write in constant memory, substituting
matches as they are found and copying everything else through verbatim. It returns the number of bytes
written.
use fuzzy_aho_corasick::{FuzzyAhoCorasickBuilder, FuzzyLimits};
let engine = FuzzyAhoCorasickBuilder::new()
.fuzzy(FuzzyLimits::new().edits(1))
.case_insensitive(true)
.build(["needle"]);
let mut out = Vec::new();
// "neeedle" has one extra 'e' (an insertion); it is replaced, the rest copied through.
engine.replace_stream("a neeedle b".as_bytes(), &mut out, 0.8, |_m| Some("X")).unwrap();
assert_eq!(String::from_utf8(out).unwrap(), "a X b");
The FuzzyReplacer turnkey form uses its configured (pattern → replacement) table:
use fuzzy_aho_corasick::{FuzzyAhoCorasickBuilder, FuzzyLimits};
let replacer = FuzzyAhoCorasickBuilder::new()
.case_insensitive(true)
.fuzzy(FuzzyLimits::new().edits(1))
.build_replacer([("hello", "hi"), ("world", "earth")]);
let mut out = Vec::new();
replacer.replace_stream("hell0 w0rld!".as_bytes(), &mut out, 0.8).unwrap();
assert_eq!(String::from_utf8(out).unwrap(), "hi earth!");
Semantics and limitations
- Per-window selection. Matches are chosen per window (as in the streaming search), so at a
window boundary overlaps are resolved left-to-right — the earlier-starting match wins — rather than
by the global ranking a whole-input
replaceuses. For inputs where matches are separated by non-matching text, the two agree exactly. - Replacement can’t borrow the match. The replacement type is independent of the match, so it may
borrow external data (e.g. a replacement table) but not the transient matched text. Return an owned
Stringif you need to derive the replacement fromm.text. - Buffer the writer. Wrap the writer in a
BufWriterfor throughput.
Parallel replace
replace_stream_parallel(reader, writer, threads, threshold, callback) fans the CPU-bound search
across a thread pool while reassembling the output in stream order on the calling thread. Because
output is inherently ordered, only the search is parallelized — the callback and writer stay on the
calling thread (no Send/Sync bounds), and the result is byte-identical to replace_stream.
use fuzzy_aho_corasick::{FuzzyAhoCorasickBuilder, FuzzyLimits};
let engine = FuzzyAhoCorasickBuilder::new().fuzzy(FuzzyLimits::new().edits(1)).build(["needle"]);
let mut out = Vec::new();
let threads = std::thread::available_parallelism().map_or(1, |n| n.get());
engine.replace_stream_parallel("a needle b".as_bytes(), &mut out, threads, 0.8, |_m| Some("X")).unwrap();
assert_eq!(String::from_utf8(out).unwrap(), "a X b");
On a 10-core machine this scales roughly 1.9× / 3.7× / 6.3× at 2 / 4 / 8 threads on a 32 MiB input —
near-linear until the serial output reassembly and memory bandwidth take over. At one thread it
matches the single-threaded form exactly, so there’s no penalty for using it when the input turns out
small. See examples/replace_bench.rs.