Idiomatic Rust API Layer
On this page
Status
Accepted
Context
Ferroni is a 1:1 C-to-Rust port of Oniguruma (ADR-001). The internal code intentionally mirrors C structure, naming, and patterns for upstream traceability. However, the public API was also C-shaped, requiring users to:
- Pass raw pointer casts (
&OnigSyntaxOniguruma as *const OnigSyntaxType) - Interpret
i32return codes that mix match positions with error codes - Construct
OnigRegionmanually and index intoVec<i32>with-1sentinels - Pass 7 parameters to
onig_search()including redundant length arguments
This made the library difficult to use for Rust developers accustomed to APIs like regex::Regex.
Decision
Add an idiomatic Rust API layer on top of the C-ported internals:
Tier 1: Public API Types (additive, zero regression risk)
RegexErrorenum (src/error.rs): Groups ~100const i32error codes into 12 semantic variants (Memory,Syntax,TimeLimitOver, etc.) withstd::error::Errorimplementation andFrom<i32>conversion.Regexwrapper (src/api.rs): WrapsRegexTypewith methods likenew(),find(),is_match(),captures(),find_iter(). Delegates toonig_new()andonig_search()internally.RegexBuilder(src/api.rs): Fluent builder for compile options (case_insensitive(),dot_matches_newline(),multi_line_anchors(),extended(),syntax()).MatchandCaptures(src/api.rs): Type-safe result types that hideOnigRegioninternals. Users getstart(),end(),as_str(),as_bytes()instead of rawbeg/endvectors.Prelude (
src/prelude.rs): Re-exportsRegex,RegexBuilder,Match,Captures,RegexErrorfor convenientuse ferroni::prelude::*.
Tier 2: Targeted Internal Improvements (low risk)
bitflags!for option flags: Replacetype OnigOptionType = u32with abitflagsstruct for type-safe option manipulation. Old constant names preserved as aliases.onig_new()returnsResult<RegexType, RegexError>: The primary compilation entry point now returns typed errors instead ofi32codes.References instead of raw pointers:
onig_new()takes&OnigSyntaxTypeinstead of*const OnigSyntaxType.onig_get_syntax()returns&OnigSyntaxType.Encapsulated
RegexTypefields: All 37pubfields changed topub(crate). External access via accessor functions or the Tier 1 wrapper.
Rationale
- Zero regression risk for Tier 1: All new code, no existing code modified. The C-ported internals remain intact for upstream traceability (ADR-001).
- Low risk for Tier 2: Each change is mechanical and verified by the full 1,695-test suite.
- User-facing impact: Reduces a 7-line C-style regex creation to a 1-line idiomatic call.
- Layered architecture: Users who need C-level control can still use
onig_new()/onig_search()directly.
Consequences
- The C-ported internal code remains structurally faithful to the C original (ADR-001 unchanged).
- New users should prefer
ferroni::prelude::Regexoverferroni::regcomp::onig_new(). - The
bitflagscrate is added as a dependency. RegexTypefields are no longer directly accessible from outside the crate; use accessor functions or theRegexwrapper.- Future API additions should follow the idiomatic layer pattern: wrap C-ported internals, don't modify them.
- The Scanner API (ADR-006) extends this layer with a domain-specific multi-pattern interface for TextMate grammar tokenization, following the same principle of wrapping C-ported internals without modification.
Amendment (2.0): the public surface
Ferroni 2.0 narrows what the crate exports. The C-internal modules are crate-private: regint, the port of regint.h, and regparse_types, the port of regparse.h. So are the internal items of the other C-port modules, and literal_trie, the Rust-only optimization of ADR-008. The public surface is then two parts and nothing else:
- The Rust API:
prelude,api,replace,scanner,error,backtrack_lintandbacktrack_rewrite, unchanged. - The C API that
oniguruma.hdeclares, at the module paths of the C-port table in the crate root. Those are theonig_*functions, the public types, theONIG_*constants and theOnigSyntax*andONIG_ENCODING_*statics.
Three rules sort the C-port items:
RegexType, C's opaqueregex_t, is re-exported fromoniguruma. So isCalloutArg, whichonig_get_arg_by_callout_argsreturns. C callers never see the fields of either.- Ferroni-only limit setters and their getters keep the style of the header's limit functions:
onig_set_ast_node_limitandonig_get_ast_node_limit,onig_set_match_stack_limitandonig_get_match_stack_limit. They stay public. - Any other item that
oniguruma.hdoes not declare is crate-private, for exampleonig_compile,onig_parse_treeandonig_regset_search_fast.
The amendment has three reasons:
- Semver. Every VM opcode and every parse-tree field was public, so each optimization that added an opcode, as ADR-008 does, was a breaking change. Private internals let the compiler and the VM change without a major release.
- C's own boundary. Oniguruma installs
oniguruma.hand nothing else.regint.handregparse.hare private headers, so their types were never part of C's public API. Ferroni now draws the same line. ADR-001 still holds: the internal modules keep their C names, structure and control flow, and only their visibility changes. - Documentation. Before this change, 1,380 public items had no docs, and 751 of them were in
regintandregparse_types. docs.rs rendered all of them.
The other 2.0 changes in the same series:
- The scanner and error types are extensible (#309).
ScannerConfig,ScannerMatch,CaptureIndex,RegexErrorandSyntaxare#[non_exhaustive], so a new field or variant is not breaking.ScannerMatch::capture_indicesis private behindScannerMatch::captures(), which removessmallvecfrom the public API. - The nesting limit is split in two (#308).
onig_set_parse_depth_limitbounds nesting only and defaults to 256 units, about 128 nested groups, down from 4,096. The newonig_set_ast_node_limitbounds the expression nodes of one pattern and defaults to 4,096. Deep patterns fail withONIGERR_PARSE_DEPTH_LIMIT_OVERbefore they can overflow the stack (ADR-013).
Migration: code that named ferroni::regint::RegexType uses ferroni::oniguruma::RegexType instead. Code that relied on the old nesting default must raise onig_set_parse_depth_limit and, for wide alternations, also onig_set_ast_node_limit.