Look-behind
Require the Date: prefix without including it in the match.
- Pattern
(?<=Date: )\d{4}-\d{2}-\d{2}- Input text · highlighted match
Date: 2026-09-26- Matched text
2026-09-26
Oniguruma regex in pure Rust.
Match, extract and replace UTF-8 text with look-behind, backreferences and subexpression calls. Ferroni ports Oniguruma’s engine to Rust, checked against its upstream tests. Add it with Cargo; no C compiler required.
Start with the behavior your application needs.
Use lookaround, backreferences, conditionals and subexpression calls. Explore matching examples.
Keep compatible ASCII and UTF-8 patterns while removing the C toolchain from your build. Check compatibility and scope.
Build editors and highlighters with the multi-pattern Scanner and UTF-16 offsets. See how scanning works.
For patterns that fit Rust’s regex crate, prefer its guaranteed linear-time matching. Ferroni uses backtracking; set limits for untrusted input.
Select a date with look-behind, then read its named captures with a familiar Rust API. The output below comes from running this example.
Run the example: cargo run --example website_sample
use ferroni::prelude::*;
fn main() -> Result<(), RegexError> {
let regex = Regex::new(r"(?<=Date: )(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})")?;
let text = "Date: 2026-09-26";
let captures = regex.captures(text).expect("the sample date matches");
println!("input: {text}");
for name in ["year", "month", "day"] {
let value = captures.name(name).expect("the named group exists");
println!("{name}: {}", value.as_str());
}
Ok(())
}
use ferroni::prelude::*;
fn main() -> Result<(), RegexError> {
let regex = Regex::new(r"(?<=Date: )(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})")?;
let text = "Date: 2026-09-26";
let captures = regex.captures(text).expect("the sample date matches");
println!("input: {text}");
for name in ["year", "month", "day"] {
let value = captures.name(name).expect("the named group exists");
println!("{name}: {}", value.as_str());
}
Ok(())
}
input: Date: 2026-09-26
year: 2026
month: 09
day: 26Each highlighted match below comes from running Ferroni. Look-behind and backreferences extend the syntax available in Rust’s regex crate; Unicode properties are useful in both engines.
Require the Date: prefix without including it in the match.
(?<=Date: )\d{4}-\d{2}-\d{2}Date: 2026-09-262026-09-26Match a word followed by the same word, using the first capture again.
Find the the repeated word.the theSelect Greek letters by their Unicode property instead of listing characters.
Hello, κόσμος!κόσμοςUse these patterns with the Regex API · Run the walkthrough example
Measure the regex work your application needs: validation, capture extraction, replacement and Unicode text processing. These comparisons use patterns and test inputs from projects including Django, Fluent Bit, Rails and WordPress, covering simple expressions, complex patterns and advanced Oniguruma features.
The measured work is each project’s regex step. Complete validators, parsers and rendering pipelines include additional work.
Ferroni brings Oniguruma’s pattern behavior to a Rust-only build. PCRE2 also supports lookaround, backreferences and recursion; the dialect and implementation language still differ. Rust regex offers guaranteed linear-time matching with a smaller syntax. These are selected features of the configurations used in the comparison.
Yes means the illustrated syntax returns the expected match. No means that syntax is rejected or its behavior differs; a rewritten expression may solve the same task. PCRE2 uses UTF and UCP, and its interpreter and JIT both pass its supported examples. fancy-regex uses Oniguruma mode; regexr uses Unicode mode with JIT off. This is a feature comparison, not a claim that all accepted patterns have identical semantics. On small screens, scroll the matrix horizontally.
Variable-length lookbehind here includes an unbounded \s*. PCRE2 supports bounded variable-length lookbehind. Multi-character case folding means, for example,strasse matching straße with case ignored. Unicode word segmentation uses UAX #29 rather than the ordinary \b word-character boundary.
The performance corpus measures real regex steps and covers only part of this syntax. Absent repeaters and recursion-level backreferences have no timings in that corpus. Ferroni’s value is preserving Oniguruma behavior in Rust, with current Unicode data and a TextMate Scanner API.
Sources: Oniguruma syntax, PCRE2 syntax, Rust regex, regexr 0.6.1. The executable matrix checks use the pinned dependencies and native sources. See compatibility and scope.
Against C Oniguruma, Ferroni’s standard API is 1.40× as fast on ARM/macOS and 1.13× as fast on x86/Linux in the aggregate for repeated use. Individual cases tell a more varied story: 20 of 21 favor Ferroni on ARM. On Linux, 9 favor Ferroni, 7 favor C, and 5 are inconclusive.
Ferroni is the reference in every cell. 1× means equal throughput. Green values above 1 mean Ferroni is faster; red values below 1 mean Ferroni is slower. For example, 0.50× means half the throughput and 2× means twice the throughput.
Each row compares its own shared tasks; rows with different case pools are not a leaderboard. Coverage appears below each factor. Task families have equal weight. These figures describe the selected corpus, not a production traffic mix or a guarantee for every pattern.
Preparation changes the comparison. C Oniguruma takes about 9% less time to compile on ARM and 13% less on Linux in this corpus. PCRE2 JIT leads every source-backed reuse case on both platforms; fancy-regex has a similar aggregate reuse time to Ferroni. The tested Rust regex configuration covers fewer of these tasks and offers guaranteed linear-time matching for its supported syntax.
The API path also matters. Ferroni’s standard methods can reuse cached match workspace. Fallible *_with searches with empty options currently create fresh workspace and take about 28% more time on ARM and 29% more on Linux for reuse. Both paths return the same checked output; replacement uses the standard API in both. The detailed tables show this profile separately. Use the fallible methods when your application needs execution errors returned explicitly.
Blacksmith run: October 14, 2026. 21 source-backed regex tasks, 513 test inputs, 3 process repetitions on one VM per platform. Only engines that return the required output are timed. Scanner calls, full highlighting and memory usage are outside this measurement.
In the separate o200k_base pre-tokenization case, regexr’s JIT-off profile reaches 1.20× Ferroni’s throughput on ARM and 1.21× on Linux. These tokenizer results have their own scope and do not enter the 21-task aggregate above.
regexr uses a separate paired Blacksmith run with a freshly measured Ferroni baseline. Unicode mode is enabled. Its JIT-enabled selector can fall back to a non-JIT backend; the detailed evidence records the choice.
Inspect every case, preparation costs and measurement ranges
A highlighter asks, at every position in a line, which of a grammar’s many patterns matches next. Ferroni’s Scanner answers with the API of vscode-oniguruma, the engine under vscode-textmate and Shiki, UTF-16 offsets included.
Scanners built from one pattern cache compile each distinct pattern once, and a string id lets a scanner reuse what it learned about a line. The Scanner API in the guide. Scanner performance and complete Ferriki highlighting have their own workload boundaries; the regex measurements above cover neither.
use ferroni::prelude::*;
let mut scanner = Scanner::new(&[
r"\b(function|const|let|var)\b",
r#""[^"]*""#,
r"//.*$",
]).unwrap();
let m = scanner
.find_next_match(r#"const x = "hello""#, 0, ScannerFindOptions::NONE)
.unwrap();
assert_eq!(m.index, 0); // "const" matched firstconst x = "hello" // greeting\b(function|const|let|var)\b"[^"]*"//.*$0: pattern 0 selects const at UTF-16 offsets 0–5.5: pattern 1 selects "hello" at UTF-16 offsets 10–17.17: pattern 2 selects // greeting at UTF-16 offsets 18–29.All patterns compete on each search. The earliest match wins; a tie goes to the earlier pattern in the list. Continue at the previous match’s end. This ASCII line has the same byte and UTF-16 offsets.
Ferroni is memory-safe, but matching uses backtracking. Set a timeout or retry limit for untrusted input, and use the compile-time warnings to reject risky patterns. Memory safety does not guarantee bounded matching time.
Pattern compilation, matching and the scanner are fuzzed on every pull request. Untrusted input in the guide · The unsafe code policy
use std::time::Duration;
use ferroni::prelude::*;
let re = Regex::new(r"(a+)+b").unwrap();
let options = SearchOptions::new()
.timeout(Duration::from_millis(50));
let hostile = "a".repeat(40);
let result = re.find_with(&hostile, options);
assert!(matches!(result, Err(RegexError::TimeLimitOver)));use ferroni::prelude::*;
let re = Regex::new(r"(a+)+$").unwrap();
for warning in re.backtracking_warnings() {
eprintln!("{warning}"); // nested unbounded repeat: …
}
// Refuse risky patterns that users supply.
let strict = Regex::builder(r"(a+)+$")
.reject_backtracking_risks(true)
.build();
assert!(strict.is_err());Ferroni carries Oniguruma forward after its C project closed in April 2025. The port preserves module structure, function names and control flow, and passes every upstream UTF-8 test. Unicode tables and the Rust API extend that foundation.
Line coverage is gated in CI. Benchmark comparisons require the same checked output before timing. How compatibility is checked
Ferroni ports ASCII and UTF-8, two of Oniguruma’s 29 encodings, and leaves out the POSIX and GNU APIs (ADR-003, ADR-012). For the projects built on Oniguruma’s syntax, that means:
vscode-oniguruma compiles every pattern as UTF-8. Ferroni's scanner keeps its API shape and its UTF-16 offsets.
jq matches on UTF-8 text, which Ferroni handles in full.
mb_ereg in Shift_JIS, EUC-JP or another non-UTF-8 encoding is not covered.
Ruby runs Onigmo, a fork of Oniguruma with its own history and encodings.
Ferroni’s scope is Oniguruma syntax for ASCII and UTF-8, plus the vscode-oniguruma scanner. No feature work is planned.
Fix bugs and parity differences against C Oniguruma.
Follow the annual Unicode Character Database release.
Keep the Rust toolchain and dependencies working. Raise the MSRV only when a fix or dependency requires it, with release notes.
Security fixes target the latest release. Report suspected vulnerabilities privately through the security policy.
The other 27 encodings (ADR-003), the POSIX and GNU APIs (ADR-012) and opt-in Onigmo syntax are outside the planned scope.
Issues are welcome and triaged as time allows. Pull requests must keep structural parity with C Oniguruma. The help wanted issues identify contribution opportunities, including the syntect backend integration.
Ferroni supplies the regex engine for Ferriki, whose Shiki-compatible highlighter tokenizes code with TextMate grammars. Each tool also works on its own.
The people who build the engines also integrate them, support them, and maintain them for the long run.