pub struct MatchOptions {
pub max_steps: Option<u64>,
pub max_depth: Option<usize>,
}Expand description
Resource limits for recognition.
Fields§
§max_steps: Option<u64>Give up after this many steps, rather than running unboundedly.
Default None. Exceeding it is MatchError::StepLimit, never a silent rejection:
“does not match” and “could not decide” are different answers (D16).
max_depth: Option<usize>Give up after recursing this deep. Default DEFAULT_MAX_DEPTH.
§Why this one has a default when max_steps does not
Exceeding a step limit returns MatchError::StepLimit; exceeding the stack aborts
the process, and no caller can catch that or report it as “could not decide”. An oracle
is fed adversarial input by design, so the safe default is a limit rather than a crash.
§Depth, stack, and how they relate
Depth counts grammar nodes entered, not input characters. Nesting in the input turns
into depth because a recursive rule re-enters itself, at a rate the grammar fixes: for
RFC 8259 one level of [ costs six nodes, so the default allows JSON nested about 40
deep. A flat input costs almost nothing however long it is — length is not the problem,
nesting is.
Each level costs roughly 2 KB of stack, measured on a debug build; a release build uses less. That fixes the ceiling at about one level per 2 KB:
| stack | deepest safe max_depth |
|---|---|
| 512 KB | 128 |
| 1 MB | 400 |
2 MiB — what std::thread gives a spawned thread | 900 |
| 64 MB | ~30,000 |
The default sits under the 1 MB row, so it is safe wherever the recognizer is called from. Raising it means giving the recognizer a bigger stack to match:
std::thread::Builder::new()
.stack_size(64 * 1024 * 1024)
.spawn(move || {
let mut recognizer = Recognizer::new(&grammar, "a").with_options(MatchOptions {
max_depth: Some(40_000),
..MatchOptions::default()
});
recognizer.accepts("start")
})?
.join()
.expect("no overflow")?;None removes the limit, which is only safe if the input’s nesting is already bounded
by something else.