Loading pageā¦
Rust walkthroughs
Loading pageā¦
nom::error::VerboseError for detailed parser debugging information?nom::error::VerboseError is an error type that accumulates the complete parsing context at each failure pointārecording the input slice, error kind, and parser chain positionāenabling rich error messages that show exactly where and why parsing failed. The default nom error type (I, ErrorKind) captures only the input position and error category, losing critical debugging information when parsers compose and backtrack. VerboseError instead builds an error chain as parsing descends through combinators, preserving the context needed to report "expected digit at position 5, but found 'x'" rather than just "invalid digit" at an opaque location. This makes parser development significantly easier: when a complex parser fails, VerboseError reveals the exact combinator and input position where the failure occurred, rather than leaving the developer guessing which branch of a deeply nested parser tree rejected the input.
use nom::{IResult, error::{Error, ErrorKind}, bytes::complete::tag, character::complete::digit1};
// Default error type: (Input, ErrorKind)
type DefaultError<'a> = (&'a str, ErrorKind);
fn parse_number(input: &str) -> IResult<&str, &str> {
digit1(input)
}
fn main() {
let result = parse_number("abc");
match result {
Err(e) => println!("Default error: {:?}", e),
_ => println!("Success"),
}
// Output: Error(("abc", Digit))
}The default error type shows the input and error kind but loses context.
use nom::{IResult, bytes::complete::tag, character::complete::digit1};
use nom::error::{VerboseError, context};
fn parse_number(input: &str) -> IResult<&str, &str, VerboseError<&str>> {
context("number", digit1)(input)
}
fn main() {
let result = parse_number("abc");
match result {
Err(nom::Err::Error(e)) => {
println!("Verbose error: {:?}", e);
// Contains context about where parsing failed
}
Err(nom::Err::Failure(e)) => {
println!("Fatal error: {:?}", e);
}
Ok((remaining, output)) => {
println!("Ok: remaining={}, output={}", remaining, output);
}
}
}VerboseError accumulates context as parsing progresses through combinators.
use nom::error::VerboseError;
fn main() {
// VerboseError contains:
// - A vector of (input, VerboseErrorKind) pairs
// - Each pair represents a failure point in the parsing chain
// VerboseErrorKind has variants:
// - Context(&'static str): Named context from context() combinator
// - Nom(ErrorKind): Original nom error kind
let errors: Vec<(&str, nom::error::VerboseErrorKind)> = vec
![];
let verbose_error = VerboseError { errors };
println!("VerboseError is a Vec of (input, error_kind) pairs");
println!("This chain allows tracing back through failures");
}VerboseError stores a chain of failures, each with input position and error kind.
use nom::{
IResult,
bytes::complete::tag,
character::complete::{digit1, alpha1},
sequence::preceded,
error::{VerboseError, context},
};
fn parse_id(input: &str) -> IResult<&str, &str, VerboseError<&str>> {
context(
"id_parser",
preceded(
context("prefix", tag("ID:")),
context("value", alpha1)
)
)(input)
}
fn main() {
let result = parse_id("ID:123");
match result {
Err(nom::Err::Error(e)) => {
println!("Error chain:");
for (input, kind) in e.errors {
println!(" at '{}' : {:?}", input, kind);
}
// Shows context chain: id_parser -> prefix -> value
// Points to where "123" failed alpha1
}
Ok((remaining, output)) => {
println!("Parsed: '{}' remaining: '{}'", output, remaining);
}
_ => {}
}
}The context combinator adds named checkpoints to the error chain.
use nom::{
IResult,
bytes::complete::tag,
character::complete::digit1,
sequence::tuple,
error::{Error, VerboseError, context},
};
// Parser with default error type
fn parse_default(input: &str) -> IResult<&str, (&str, &str), Error<&str>> {
tuple((tag("hello"), digit1))(input)
}
// Parser with verbose error type
fn parse_verbose(input: &str) -> IResult<&str, (&str, &str), VerboseError<&str>> {
context("full_parser",
tuple((
context("greeting", tag("hello")),
context("number", digit1)
))
)(input)
}
fn main() {
let bad_input = "hello world";
// Default error: minimal information
match parse_default(bad_input) {
Err(nom::Err::Error(e)) => {
println!("Default error:");
println!(" Input: {:?}", e.input);
println!(" Kind: {:?}", e.code);
}
_ => {}
}
// Verbose error: full context chain
match parse_verbose(bad_input) {
Err(nom::Err::Error(e)) => {
println!("\nVerbose error:");
for (input, kind) in e.errors {
println!(" Input: '{}'", input);
println!(" Error: {:?}", kind);
println!(" ---");
}
}
_ => {}
}
}Default errors show one failure; verbose errors show the entire failure chain.
use nom::{
IResult,
bytes::complete::tag,
character::complete::digit1,
error::{VerboseError, context},
};
fn parse_item(input: &str) -> IResult<&str, &str, VerboseError<&str>> {
context("item",
context("item_prefix", tag("item:"))
)(input)
}
fn format_error(input: &str, error: VerboseError<&str>) -> String {
let mut result = String::new();
for (error_input, error_kind) in error.errors {
// Find position in original input
let offset = input.len() - error_input.len();
result.push_str(&format!(
"at offset {} ({:?}): '{}'\n",
offset,
error_kind,
error_input.chars().take(20).collect::<String>()
));
}
result
}
fn main() {
let input = "item";
let result = parse_item(input);
match result {
Err(nom::Err::Error(e)) => {
println!("{}", format_error(input, e));
}
Ok((remaining, matched)) => {
println!("Matched: '{}', remaining: '{}'", matched, remaining);
}
_ => {}
}
}Custom formatting extracts offset positions and error context for user messages.
use nom::{
IResult,
bytes::complete::tag,
character::complete::{alpha1, digit1},
sequence::tuple,
branch::alt,
error::{VerboseError, context},
};
fn parse_record(input: &str) -> IResult<&str, (&str, &str), VerboseError<&str>> {
context("record",
tuple((
context("name", alpha1),
context("separator", tag(",")),
context("age", digit1)
))
)(input)
}
fn main() {
// Missing separator
let result = parse_record("John25");
match result {
Err(nom::Err::Error(e)) => {
println!("Error chain for 'John25':");
for (input, kind) in &e.errors {
println!(" Input '{}' : {:?}", input, kind);
}
// Chain shows:
// 1. separator expected but '2' found
// 2. record context
// 3. name succeeded, separator failed
}
_ => {}
}
// Invalid age
let result = parse_record("John,abc");
match result {
Err(nom::Err::Error(e)) => {
println!("\nError chain for 'John,abc':");
for (input, kind) in e.errors {
println!(" Input '{}' : {:?}", input, kind);
}
}
_ => {}
}
}Each nested combinator adds context to the error chain as parsing unwinds.
use nom::error::VerboseErrorKind;
fn main() {
// VerboseErrorKind has two variants:
// ContextErr: Named context from context() combinator
let context_err = VerboseErrorKind::Context("user_id");
println!("Context error: {:?}", context_err);
// Nom: Original nom ErrorKind
let nom_err = VerboseErrorKind::Nom(nom::error::ErrorKind::Tag);
println!("Nom error: {:?}", nom_err);
// Common ErrorKind values:
// - Tag: Expected literal string
// - Char: Expected specific character
// - Digit: Expected digit
// - Alpha: Expected alphabetic character
// - Many0, Many1: Repetition errors
// - Alt: All alternatives failed
}VerboseErrorKind distinguishes between named contexts and base nom errors.
use nom::{
IResult,
bytes::complete::tag,
character::complete::{alpha1, digit1, multispace0, char},
sequence::{preceded, tuple},
branch::alt,
error::{VerboseError, context},
};
#[derive(Debug)]
struct Person {
name: String,
age: u32,
}
fn parse_person(input: &str) -> IResult<&str, Person, VerboseError<&str>> {
context("person",
tuple((
context("name", alpha1),
context("separator", preceded(multispace0, tag(","))),
context("age", preceded(multispace0, digit1))
))
)(input)
.map(|(remaining, (name, _, age))| {
(remaining, Person {
name: name.to_string(),
age: age.parse().unwrap_or(0),
})
})
}
fn main() {
let test_cases = [
"John,25", // Valid
"John, 25", // Valid with spaces
"John", // Missing separator and age
"John,abc", // Invalid age
"123,25", // Invalid name
];
for input in test_cases {
println!("\nParsing: '{}'", input);
match parse_person(input) {
Ok((remaining, person)) => {
println!(" Success: {:?}", person);
println!(" Remaining: '{}'", remaining);
}
Err(nom::Err::Error(e)) => {
println!(" Error chain:");
for (err_input, err_kind) in e.errors {
println!(" '{:.20}...' : {:?}",
err_input, err_kind);
}
}
Err(nom::Err::Failure(e)) => {
println!(" Fatal error:");
for (err_input, err_kind) in e.errors {
println!(" '{:.20}...' : {:?}",
err_input, err_kind);
}
}
_ => {}
}
}
}Named contexts make it clear which parser component failed.
use nom::{
IResult,
bytes::complete::tag,
character::complete::digit1,
combinator::peek,
error::{VerboseError, context, make_error},
Err as NomErr,
};
fn parse_with_failure(input: &str) -> IResult<&str, &str, VerboseError<&str>> {
// Error: recoverable, allows backtracking
// Failure: unrecoverable, stops immediately
// Use Err::Failure for errors that should not be backtracked past
// This is useful when partial parsing has side effects
context("parser", digit1)(input)
}
fn main() {
// nom::Err::Error - recoverable, parser can try alternatives
// nom::Err::Failure - unrecoverable, stop immediately
let result = parse_with_failure("abc");
match result {
Err(NomErr::Error(e)) => {
println!("Recoverable error:");
println!(" Parser can try alternatives");
for (input, kind) in e.errors {
println!(" '{}' : {:?}", input, kind);
}
}
Err(NomErr::Failure(e)) => {
println!("Unrecoverable error:");
println!(" Stop immediately");
for (input, kind) in e.errors {
println!(" '{}' : {:?}", input, kind);
}
}
Ok(_) => println!("Success"),
_ => {}
}
}Error allows backtracking; Failure stops the parser immediately.
use nom::{
IResult,
bytes::complete::tag,
error::{VerboseError, context, ParseError, FromExternalError},
};
#[derive(Debug)]
enum CustomError {
Nom(VerboseError<String>),
Custom(String),
}
// Implement ParseError for custom type
impl<'a> ParseError<&'a str> for CustomError {
fn from_error_kind(input: &'a str, kind: nom::error::ErrorKind) -> Self {
CustomError::Nom(VerboseError::from_error_kind(input, kind))
}
fn append(input: &'a str, kind: nom::error::ErrorKind, other: Self) -> Self {
match other {
CustomError::Nom(mut e) => {
e.errors.push((input, nom::error::VerboseErrorKind::Nom(kind)));
CustomError::Nom(e)
}
CustomError::Custom(_) => other,
}
}
fn from_char(input: &'a str, c: char) -> Self {
Self::from_error_kind(input, nom::error::ErrorKind::Char)
}
}
fn main() {
println!("Custom error types can wrap VerboseError");
println!("and add application-specific error information");
}Custom error types can extend VerboseError with application-specific context.
use nom::{
IResult,
bytes::complete::{tag, take_while},
character::complete::{alpha1, digit1, char},
sequence::{tuple, delimited},
branch::alt,
multi::many0,
error::{VerboseError, context},
};
#[derive(Debug)]
struct Config {
name: String,
version: String,
items: Vec<String>,
}
fn parse_name(input: &str) -> IResult<&str, &str, VerboseError<&str>> {
context("name_field",
delimited(
context("name_prefix", tag("name=")),
context("name_value", alpha1),
context("name_newline", char('\n'))
)
)(input)
}
fn parse_version(input: &str) -> IResult<&str, &str, VerboseError<&str>> {
context("version_field",
delimited(
context("version_prefix", tag("version=")),
context("version_value", take_while(|c: char| c.is_alphanumeric() || c == '.')),
context("version_newline", char('\n'))
)
)(input)
}
fn parse_item(input: &str) -> IResult<&str, &str, VerboseError<&str>> {
context("item",
delimited(
context("item_prefix", tag("- ")),
context("item_value", alpha1),
context("item_newline", char('\n'))
)
)(input)
}
fn parse_config(input: &str) -> IResult<&str, Config, VerboseError<&str>> {
context("config",
tuple((parse_name, parse_version, many0(parse_item)))
)(input)
.map(|(remaining, (name, version, items))| {
(remaining, Config {
name: name.to_string(),
version: version.to_string(),
items: items.iter().map(|s| s.to_string()).collect(),
})
})
}
fn main() {
let bad_input = "name=Test\nversion=1.0\n- item1\n- item2\n- 123\n";
// The last item has digits, which fails alpha1
match parse_config(bad_input) {
Ok((remaining, config)) => {
println!("Success: {:?}", config);
}
Err(nom::Err::Error(e)) => {
println!("Parsing failed:");
for (input, kind) in e.errors {
// Find which context failed
println!(" Input: '{}'", input.lines().next().unwrap_or(""));
println!(" Kind: {:?}", kind);
println!();
}
// The error chain shows:
// - item_value expected alpha, found '123'
// - item context failed
// - config context has error
}
_ => {}
}
}Detailed context at each level pinpoints exactly where complex parsers fail.
use nom::{
IResult,
bytes::complete::tag,
character::complete::alpha1,
sequence::preceded,
error::{VerboseError, context},
};
fn parse_greeting(input: &str) -> IResult<&str, &str, VerboseError<&str>> {
context("greeting",
preceded(
context("greeting_prefix", tag("Hello, ")),
context("greeting_name", alpha1)
)
)(input)
}
fn describe_error(input: &str, error: VerboseError<&str>) -> String {
use nom::error::VerboseErrorKind;
let mut description = String::new();
let original = input;
for (error_input, kind) in error.errors {
let offset = original.len() - error_input.len();
let position = if offset < original.len() {
format!("position {} ('{}')", offset, &original[offset..offset+1.min(original.len()-offset)])
} else {
format!("position {} (end)", offset)
};
match kind {
VerboseErrorKind::Context(ctx) => {
description.push_str(&format!(
"In '{}': expected input at {}\n",
ctx, position
));
}
VerboseErrorKind::Nom(err_kind) => {
description.push_str(&format!(
"Failed {:?} at {}\n",
err_kind, position
));
}
}
}
description
}
fn main() {
let input = "Hello, 123";
match parse_greeting(input) {
Err(nom::Err::Error(e)) => {
println!("{}", describe_error(input, e));
}
Ok((remaining, matched)) => {
println!("Matched: '{}', remaining: '{}'", matched, remaining);
}
_ => {}
}
}Reconstructing the error chain produces human-readable error messages.
use nom::{
IResult,
bytes::complete::tag,
character::complete::digit1,
error::{Error, VerboseError},
};
fn parse_with_error(input: &str) -> IResult<&str, &str, Error<&str>> {
// Error type: minimal overhead, no allocation
// Good for production where errors are handled simply
tag("prefix")(input)
}
fn parse_with_verbose(input: &str) -> IResult<&str, &str, VerboseError<&str>> {
// VerboseError: allocates Vec for error chain
// More overhead but better error messages
// Good for development and user-facing errors
tag("prefix")(input)
}
fn main() {
let input = "prefix123";
// Production: use Error for speed
let _: IResult<&str, &str, Error<&str>> = parse_with_error(input);
// Development: use VerboseError for debugging
let _: IResult<&str, &str, VerboseError<&str>> = parse_with_verbose(input);
println!("Error<T>: Minimal overhead, simple errors");
println!("VerboseError<T>: More overhead, rich context");
}VerboseError has allocation overhead; use Error for production when detailed messages aren't needed.
use nom::{
IResult,
bytes::complete::tag,
character::complete::digit1,
error::{Error, VerboseError, context},
};
fn parse_simple(input: &str) -> IResult<&str, &str, Error<&str>> {
tag("hello")(input)
}
fn parse_verbose(input: &str) -> IResult<&str, &str, VerboseError<&str>> {
context("greeting", tag("hello"))(input)
}
fn main() {
// Convert from simple to verbose if needed
let result = parse_simple("world");
match result {
Err(nom::Err::Error(e)) => {
// Can convert Error to VerboseError if needed
let verbose = VerboseError::from_error_kind(e.input, e.code);
println!("Converted to verbose: {:?}", verbose);
}
_ => {}
}
// Or use VerboseError from the start for development
let result = parse_verbose("world");
match result {
Err(nom::Err::Error(e)) => {
println!("Already verbose: {:?}", e);
}
_ => {}
}
}Start with VerboseError for development; consider Error for production if needed.
use nom::{
IResult,
bytes::complete::tag,
character::complete::{alpha1, digit1, multispace0},
sequence::{tuple, preceded},
error::{VerboseError, context},
};
fn parse_assignment(input: &str) -> IResult<&str, (&str, &str), VerboseError<&str>> {
context("assignment",
tuple((
context("identifier", alpha1),
context("whitespace1", multispace0),
context("equals", tag("=")),
context("whitespace2", multispace0),
context("value", digit1)
))
)(input)
.map(|(remaining, (id, _, _, _, val))| {
(remaining, (id, val))
})
}
fn report_error(input: &str, error: VerboseError<&str>) -> String {
use nom::error::VerboseErrorKind;
let mut lines = input.lines().enumerate().collect::<Vec<_>>();
let mut report = String::new();
for (error_input, kind) in error.errors.iter().rev() {
let offset = input.len() - error_input.len();
// Find line and column
let mut current_offset = 0;
let mut line_num = 1;
let mut col = 1;
for (i, c) in input.char_indices() {
if i >= offset {
break;
}
if c == '\n' {
line_num += 1;
col = 1;
} else {
col += 1;
}
}
let snippet = input.lines().nth(line_num - 1).unwrap_or("");
match kind {
VerboseErrorKind::Context(ctx) => {
report.push_str(&format!(
"at line {} column {} in context '{}'\n",
line_num, col, ctx
));
report.push_str(&format!(" {}\n", snippet));
report.push_str(&format!(" {}^\n", " ".repeat(col - 1)));
}
VerboseErrorKind::Nom(err_kind) => {
report.push_str(&format!(
"at line {} column {}: {:?}\n",
line_num, col, err_kind
));
}
}
}
report
}
fn main() {
let input = "x = abc";
match parse_assignment(input) {
Ok((remaining, (id, val))) => {
println!("Parsed: {} = {}", id, val);
}
Err(nom::Err::Error(e)) => {
println!("{}", report_error(input, e));
}
_ => {}
}
}Combining VerboseError with line/column calculation produces user-friendly error reports.
VerboseError components:
| Component | Purpose |
|-----------|---------|
| errors: Vec<(I, VerboseErrorKind)> | Chain of failure points |
| VerboseErrorKind::Context(&str) | Named context from context() |
| VerboseErrorKind::Nom(ErrorKind) | Base nom error types |
Error chain accumulation:
| Combinator | Effect on Error Chain |
|------------|----------------------|
| context(name, parser) | Adds named context on failure |
| alt(p1, p2, ...) | Accumulates errors from all alternatives |
| tuple(p1, p2, ...) | Shows failure at first failing parser |
| many0, many1 | Context from failing element |
Error types in nom:
| Type | Use Case | Overhead |
|------|----------|----------|
| (I, ErrorKind) | Minimal, production | None |
| Error<I> | Simple error handling | Small |
| VerboseError<I> | Development, debugging | Vec allocation |
| Custom types | Application-specific | Varies |
Key insight: VerboseError transforms parser debugging from "something failed somewhere" to "the digit1 parser in the age context failed at position 42 expecting a digit but found 'x'." This information is invaluable during parser development, where combinators nest deeply and failures can occur at any level. The error chain records the complete path from the outermost parser down to the failing leaf, accumulating (input, error_kind) pairs at each context() wrapper and each failure point. When an alt combinator tries multiple alternatives, VerboseError collects errors from all branches, showing why each one failed. The trade-off is allocationāVerboseError builds a Vec of error pairs, while the base error type carries no allocation. For development and user-facing error reporting, this overhead is negligible; for hot paths in production with simple error handling, consider the base error type. The context() combinator is the key tool: wrap parsers at meaningful boundaries with context("parser_name", parser) to annotate where failures occur. Without context, error chains show only Nom(ErrorKind) values; with context, each level is labeled, making it clear whether the failure was in name, separator, or age rather than just "expected digit at offset 5."