How does nom::branch::alt differ from or for trying multiple parser alternatives?
nom::branch::alt tries each parser in sequence until one succeeds or all fail, returning the first successful result, while or (the | operator trait) combines two parsers into a single parser that tries the left side first and falls back to the right side if the left failsâbut alt supports any number of parsers and is more idiomatic for parsing combinators. The key distinction is that alt is a combinator function designed specifically for nom's error handling model, supporting multiple alternatives with proper backtracking semantics, while or is a binary operator that only combines two parsers.
Parser Combinators and Alternatives
use nom::{IResult, branch::alt, bytes::complete::tag, character::complete::alpha1};
// Parser combinators build complex parsers from simple ones
// When parsing, you often need to try multiple alternatives
fn parse_keyword_or_identifier(input: &str) -> IResult<&str, &str> {
// Try to match a keyword, or fall back to identifier
// This is the "alternation" pattern
// Keywords: "let", "fn", "if", "else"
// Identifiers: alphabetic strings
alt((
tag("let"),
tag("fn"),
tag("if"),
tag("else"),
alpha1,
))(input)
}
fn example() {
let input = "let x = 5";
let (remaining, matched) = parse_keyword_or_identifier(input).unwrap();
assert_eq!(matched, "let");
assert_eq!(remaining, " x = 5");
}Parser alternatives are fundamental in parsingâkeywords, operators, or multiple syntax forms need to be tried in order.
The alt Combinator
use nom::{IResult, branch::alt, bytes::complete::tag, character::complete::{alpha1, digit1}};
fn alt_example() {
// alt takes a tuple of parsers
// Tries each in order until one succeeds
let mut parser = alt((
tag("hello"),
tag("world"),
alpha1, // Fallback: any alphabetic string
));
// "hello there" matches first alternative
assert_eq!(parser("hello there"), Ok((" there", "hello")));
// "world!" matches second alternative
assert_eq!(parser("world!"), Ok(("!", "world")));
// "foo" matches third alternative (alpha1)
assert_eq!(parser("foo bar"), Ok((" bar", "foo")));
// "123" fails all alternatives
assert!(parser("123").is_err());
}alt takes a tuple of parsers and tries them in sequence, returning the first successful result.
The or Method (| Operator)
use nom::{IResult, bytes::complete::tag, character::complete::alpha1, error::Error};
// nom's Parser trait implements the | operator for alternation
// This is the "or" method
fn or_example() {
// The | operator combines two parsers
// This calls the "or" method internally
let mut parser = tag::<_, _, Error<_>>("hello")
.or(tag("world"));
// Same behavior as alt with two parsers
assert_eq!(parser("hello"), Ok(("", "hello")));
assert_eq!(parser("world"), Ok(("", "world")));
assert!(parser("foo").is_err());
}The | operator (via the Parser trait's or method) combines exactly two parsers into an alternative.
Key Difference: Number of Alternatives
use nom::{IResult, branch::alt, bytes::complete::tag, Parser, error::Error};
fn number_of_alternatives() {
// alt supports any number of parsers (as a tuple)
let multi_alt = alt((
tag("a"),
tag("b"),
tag("c"),
tag("d"),
tag("e"),
));
// or/| only combines two parsers
let binary_or = tag::<_, _, Error<_>>("a")
.or(tag("b"));
// To combine more with or, chain them:
let chained_or = tag::<_, _, Error<_>>("a")
.or(tag("b"))
.or(tag("c"))
.or(tag("d"))
.or(tag("e"));
// alt is cleaner for multiple alternatives
// Chained or is verbose but equivalent
}alt handles multiple parsers naturally; or must be chained for more than two alternatives.
alt as a Function vs or as a Method
use nom::{IResult, branch::alt, bytes::complete::tag, character::complete::alpha1};
fn function_vs_method() {
// alt is a function that takes a tuple
// alt((parser1, parser2, parser3, ...))
let alt_parser = |input| {
alt((
tag("if"),
tag("else"),
tag("while"),
))(input)
};
// or is a method on the Parser trait
// parser1.or(parser2)
let or_parser = |input| {
tag::<_, _, nom::error::Error<_>>("if")
.or(tag("else"))
.or(tag("while"))
(input)
};
// Functional style: alt((a, b, c))
// Method style: a.or(b).or(c)
// Both produce equivalent results
// alt is idiomatic for multiple alternatives
}alt is a free function taking a tuple; or is a method on the Parser trait.
Error Handling Differences
use nom::{IResult, branch::alt, bytes::complete::tag, error::{Error, ErrorKind}};
fn error_handling() {
let input = "xyz";
// alt with multiple failing parsers
let result = alt::<_, _, Error<&str>, _>((
tag("hello"),
tag("world"),
tag("foo"),
))(input);
// When all alternatives fail:
// - The error reports the "most advanced" error
// - This is the error from the parser that consumed the most input
// This helps with error reporting
// If you have alternatives that partially match, the error
// from the longest partial match is more useful
// or/| handles errors similarly for each pair
// But alt handles the aggregate more carefully
}Both alt and or use nom's error accumulation model, but alt handles multiple alternatives in a unified way.
Backtracking Behavior
use nom::{IResult, branch::alt, bytes::complete::tag, sequence::preceded};
fn backtracking() {
// alt and or both backtrack on failure
// When one alternative fails, input is restored for the next
let mut parser = alt((
preceded(tag("hello"), tag(" world")), // "hello world"
tag("hello"), // Just "hello"
));
// First alternative requires "hello world"
// If input is "hello" (without " world"), first fails
// Second alternative is tried with original input
let result = parser("hello");
// First alternative fails, second succeeds
assert_eq!(result, Ok(("", "hello")));
// The input is restored ("hello" not consumed by failed first alt)
}Both alt and or properly backtrackârestoring input position when an alternative fails.
Complete vs Streaming Mode
use nom::{IResult, branch::alt, bytes::complete::tag, bytes::streaming::tag as streaming_tag};
fn complete_vs_streaming() {
// For complete input (all data available):
let complete_parser = alt((
tag("hello"),
tag("world"),
));
// For streaming input (partial data):
// Use streaming combinators
// alt works with streaming parsers too:
let streaming_parser = alt((
streaming_tag("hello"),
streaming_tag("world"),
));
// The difference is in how "incomplete" vs "error" is handled
// Complete parsers: error if no match
// Streaming parsers: may return Incomplete if need more data
}Both alt and or work with complete and streaming parsersâthe underlying parsers determine the mode.
Real-World Example: Parsing Expressions
use nom::{
IResult, branch::alt,
bytes::complete::tag,
character::complete::{alpha1, digit1, space0},
sequence::{delimited, preceded},
multi::many0,
};
// Parse simple arithmetic expressions
// expr := term (('+' | '-') term)*
// term := factor (('*' | '/') factor)*
// factor := number | variable | '(' expr ')'
fn parse_factor(input: &str) -> IResult<&str, &str> {
alt((
digit1, // Number
alpha1, // Variable
delimited(tag("("), parse_expr, tag(")")), // Parenthesized
))(input)
}
fn parse_term(input: &str) -> IResult<&str, &str> {
// Factor followed by (* /) factors
// Simplified for example
parse_factor(input)
}
fn parse_expr(input: &str) -> IResult<&str, &str> {
// Term followed by (+ -) terms
// Simplified for example
parse_term(input)
}
fn expression_parsing() {
// alt is essential for expression grammars
// Multiple alternatives at each precedence level
assert_eq!(parse_factor("123"), Ok(("", "123")));
assert_eq!(parse_factor("x"), Ok(("", "x")));
// Parenthesized expressions also work via alt
}Expression parsing uses alt extensively for operator alternatives and different expression forms.
Combining with Other Combinators
use nom::{
IResult, branch::alt, bytes::complete::tag,
character::complete::{alpha1, digit1},
sequence::tuple,
multi::many0,
};
fn combined_with_sequence() {
// alt inside tuple for complex patterns
let mut parser = tuple((
alpha1,
alt((tag("="), tag(":="), tag("=="))), // Multiple operators
many0(digit1),
));
let result = parser("x:=123").unwrap();
// ("", ('x', ":=", ["123"]))
}
fn combined_with_many() {
// alt inside many0 for repeated alternatives
let mut parser = many0(alt((
tag("a"),
tag("b"),
)));
let result = parser("abab").unwrap();
// ("", ["a", "b", "a", "b"])
}alt composes naturally with other combinators like tuple, many0, preceded, etc.
The Parser Trait and or Method
use nom::{IResult, Parser, bytes::complete::tag, error::Error};
fn parser_trait() {
// The Parser trait provides the or method
// It's implemented for all parser functions
// or takes another parser and returns a combined parser
fn parser1(input: &str) -> IResult<&str, &str> {
tag("hello")(input)
}
fn parser2(input: &str) -> IResult<&str, &str> {
tag("world")(input)
}
// Use or method:
let combined = parser1.or(parser2);
// This creates a new parser that tries parser1, then parser2
// The | operator is syntactic sugar for or:
// parser1.or(parser2) is equivalent to parser1 | parser2
// (when the trait is in scope)
}The Parser trait provides or as a method; the | operator is syntactic sugar for it.
When to Use alt vs or
use nom::{IResult, branch::alt, bytes::complete::tag, Parser, error::Error};
fn when_to_use() {
// Use alt when:
// 1. Multiple alternatives (> 2)
let many = alt((tag("a"), tag("b"), tag("c"), tag("d")));
// 2. Alternatives are static/known at compile time
let keywords = alt((
tag("if"),
tag("else"),
tag("while"),
tag("for"),
));
// 3. Clean, declarative syntax preferred
let tokens = alt((
tag("=="),
tag("!="),
tag("<="),
tag(">="),
tag("<"),
tag(">"),
));
// Use or when:
// 1. Only two alternatives
let binary = tag::<_, _, Error<_>>("true").or(tag("false"));
// 2. Dynamically constructed parsers
fn make_parser(s1: &str, s2: &str) -> impl Fn(&str) -> IResult<&str, &str> {
move |input| tag(s1).or(tag(s2))(input)
}
// 3. Method chaining style preferred
let chained = tag("a")
.or(tag("b"))
.or(tag("c"));
// | Use alt when | Use or when |
// |--------------|--------------|
// | > 2 alternatives | Exactly 2 alternatives |
// | Static tuples | Dynamic/chained construction |
// | Declarative style | Method chaining style |
}Use alt for multiple alternatives; use or when you specifically need method chaining or have exactly two alternatives.
Performance Considerations
use nom::{IResult, branch::alt, bytes::complete::tag, Parser};
fn performance() {
// Both alt and or compile to similar code
// The choice is primarily stylistic
// For many alternatives, order matters:
let parser = alt((
tag("long_keyword"), // Try longer first if prefix overlap
tag("long"),
tag("lon"),
tag("lo"),
tag("l"),
));
// For input "long_keyword":
// - Tries "long_keyword" first, succeeds
// - Never tries other alternatives
// If reversed order:
let bad_order = alt((
tag("l"), // Would match first!
tag("lo"), // Never tried
tag("lon"), // Never tried
tag("long"), // Never tried
tag("long_keyword"), // Never tried
));
// "long_keyword" would match "l" and return early
// Order alternatives carefully!
}Performance is similar; the main consideration is ordering alternatives from most specific to least specific.
Error Reporting with alt
use nom::{
IResult, branch::alt, bytes::complete::tag,
error::{Error, VerboseError,VerboseErrorKind},
};
fn error_reporting() {
// nom accumulates errors from all alternatives
// The error position reflects the "best" failure
// With VerboseError, you get more context:
let mut parser = alt((
tag::<_, _, VerboseError<&str>>("hello"),
tag("world"),
));
// If input is "hel", first alternative partially matches
// Error position reflects where the partial match failed
// For better error messages:
// 1. Order alternatives logically
// 2. Use VerboseError for debugging
// 3. Consider custom error types
}nom's error model tries to report the most advanced failure across all alternatives.
Summary Table
use nom::{IResult, branch::alt, bytes::complete::tag, Parser, error::Error};
fn summary() {
// | Aspect | alt | or |
// |--------|-----|-----|
// | Syntax | Function | Method/operator |
// | Alternatives | Tuple (any count) | Exactly 2 |
// | Style | Functional | Method chaining |
// | Chaining | Not needed | Must chain for >2 |
// | Backtracking | Yes | Yes |
// | Error handling | Same as or | Same as alt |
// Equivalence:
// alt((a, b, c)) ~= a.or(b).or(c)
// Both properly backtrack and handle errors
// alt is more idiomatic for multiple alternatives
}Synthesis
Quick reference:
use nom::{IResult, branch::alt, bytes::complete::tag, Parser, error::Error};
fn quick_reference() {
// alt: Function taking a tuple of parsers
let multi = alt((
tag("if"),
tag("else"),
tag("while"),
tag("for"),
));
// or: Method combining exactly two parsers
let binary = tag::<_, _, Error<_>>("true").or(tag("false"));
// Chain or for multiple alternatives:
let chained = tag::<_, _, Error<_>>("if")
.or(tag("else"))
.or(tag("while"));
// Prefer alt for 3+ alternatives
// Prefer or for exactly 2 alternatives or method chaining style
}Key insight: alt and or provide the same fundamental operationâtrying multiple parsers in sequence until one succeedsâbut differ in their interface design and typical use cases. alt is a combinator function that takes a tuple of parsers, making it natural for expressing multiple alternatives in a single declarative form: alt((tag("if"), tag("else"), tag("while"))). The or method (and | operator) is a method on the Parser trait that takes exactly one other parser: tag("if").or(tag("else")). For more than two alternatives, or must be chained: a.or(b).or(c).or(d), which is verbose compared to alt((a, b, c, d)). Both properly backtrack when an alternative fails, restoring the input position before trying the next parser, and both accumulate errors in the same way. The choice between them is primarily stylistic: use alt when you have multiple alternatives and prefer a tuple-based declarative style; use or when you have exactly two alternatives or when method chaining fits your code organization better. In practice, alt is the more commonly seen pattern in nom code because parser alternatives often involve multiple optionsâkeywords, operators, or syntax formsâand the tuple syntax makes the alternatives visually clear and easy to reorder.
