How does nom::branch::alt differ from nom::combinator::or for handling multiple parser alternatives?
nom::branch::alt accepts a tuple of parsers and tries each in sequence until one succeeds, returning early on the first match. nom::combinator::or is a binary combinator that tries exactly two parsersâtrying the first and falling back to the second if it fails. The key difference is that alt is designed for multiple alternatives with optimized error accumulation, while or is for simple two-way choices. For more than two alternatives, alt is preferred because or chains create nested error types and less efficient backtracking, whereas alt accumulates errors from all attempted parsers to provide better error messages.
Basic alt Usage
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::IResult;
fn main() {
// alt tries each parser in order until one succeeds
let parser = alt((
tag("hello"),
tag("world"),
tag("foo"),
));
// Try "hello" first
let result: IResult<&str, &str> = parser("hello world");
println!("{:?}", result); // Ok((" world", "hello"))
// "hello" fails, try "world"
let result2: IResult<&str, &str> = parser("world!");
println!("{:?}", result2); // Ok(("!", "world"))
// "hello" and "world" fail, try "foo"
let result3: IResult<&str, &str> = parser("foobar");
println!("{:?}", result3); // Ok(("bar", "foo"))
// All fail
let result4: IResult<&str, &str> = parser("baz");
println!("{:?}", result4); // Err(Error)
}alt takes a tuple of parsers and tries them in sequence.
Basic or Usage
use nom::combinator::or;
use nom::bytes::complete::tag;
use nom::IResult;
fn main() {
// or is binary - only two parsers
let parser = or(tag("hello"), tag("world"));
let result: IResult<&str, &str> = parser("hello!");
println!("{:?}", result); // Ok(("!", "hello"))
let result2: IResult<&str, &str> = parser("world!");
println!("{:?}", result2); // Ok(("!", "world"))
// or returns the error from the last parser tried
let result3: IResult<&str, &str> = parser("foo");
println!("{:?}", result3); // Err(Error from "world")
}or is a binary operator for exactly two parsers.
Chaining or for Multiple Alternatives
use nom::combinator::or;
use nom::bytes::complete::tag;
use nom::IResult;
fn main() {
// Chaining or for multiple alternatives - verbose and inefficient
let parser = or(
tag("hello"),
or(
tag("world"),
or(
tag("foo"),
tag("bar")
)
)
);
// This works but creates nested error types
let result: IResult<&str, &str> = parser("foo!");
println!("{:?}", result); // Ok(("!", "foo"))
// Each nested or wraps the error type further
// Error reporting becomes complex
}Chaining or works but creates nested structures and error types.
alt with Multiple Parsers
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::IResult;
fn main() {
// alt handles multiple parsers cleanly
let parser = alt((
tag("if"),
tag("else"),
tag("for"),
tag("while"),
tag("return"),
tag("break"),
));
// Tries each in order
assert!(parser("if x").is_ok());
assert!(parser("else {").is_ok());
assert!(parser("for i").is_ok());
assert!(parser("while true").is_ok());
// Clean error message when all fail
let result = parser("unknown");
println!("Error: {:?}", result);
}alt cleanly handles any number of parsers in a tuple.
Error Handling Differences
use nom::branch::alt;
use nom::combinator::or;
use nom::bytes::complete::tag;
use nom::error::{Error, ParseError};
use nom::IResult;
fn main() {
// alt accumulates errors from all parsers
let alt_parser = alt((
tag("hello"),
tag("world"),
tag("foo"),
));
let alt_result = alt_parser("baz");
println!("alt error: {:?}", alt_result);
// Error includes context about what was tried
// or returns error from the last parser
let or_parser = or(tag("hello"), tag("world"));
let or_result = or_parser("baz");
println!("or error: {:?}", or_result);
// Error only from "world" (the second parser)
}alt can provide better error messages by accumulating errors from all alternatives.
Performance Characteristics
use nom::branch::alt;
use nom::combinator::or;
use nom::bytes::complete::tag;
fn main() {
// alt is optimized for multiple alternatives
// - Single pass through alternatives
// - Early return on success
// - Efficient error accumulation
let alt_parser = alt((
tag("if"),
tag("else"),
tag("for"),
tag("while"),
));
// or chains create nested structures
// - Each or is a separate function call
// - Nested error types
// - More stack frames
let or_parser = or(
tag("if"),
or(
tag("else"),
or(
tag("for"),
tag("while")
)
)
);
// For 2 alternatives: or is fine
// For 3+ alternatives: alt is preferred
}alt is optimized for multiple alternatives; or chains have overhead.
Parsing Keywords Example
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::alpha1;
use nom::sequence::preceded;
use nom::IResult;
#[derive(Debug)]
enum Keyword {
If,
Else,
For,
While,
Return,
}
fn parse_keyword(input: &str) -> IResult<&str, Keyword> {
// alt with mapped results
alt((
|i| tag("if")(i).map(|(r, _)| (r, Keyword::If)),
|i| tag("else")(i).map(|(r, _)| (r, Keyword::Else)),
|i| tag("for")(i).map(|(r, _)| (r, Keyword::For)),
|i| tag("while")(i).map(|(r, _)| (r, Keyword::While)),
|i| tag("return")(i).map(|(r, _)| (r, Keyword::Return)),
))(input)
}
fn main() {
assert_eq!(parse_keyword("if x"), Ok((" x", Keyword::If)));
assert_eq!(parse_keyword("else {"), Ok((" {", Keyword::Else)));
assert_eq!(parse_keyword("for i"), Ok((" i", Keyword::For)));
}alt works well with mapped results for parsing into types.
Using with map for Cleaner Code
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::combinator::map;
use nom::IResult;
#[derive(Debug, PartialEq)]
enum Token {
Add,
Sub,
Mul,
Div,
}
fn parse_operator(input: &str) -> IResult<&str, Token> {
// Combine alt with map for clean transformations
alt((
map(tag("+"), |_| Token::Add),
map(tag("-"), |_| Token::Sub),
map(tag("*"), |_| Token::Mul),
map(tag("/"), |_| Token::Div),
))(input)
}
fn main() {
assert_eq!(parse_operator("+ rest"), Ok((" rest", Token::Add)));
assert_eq!(parse_operator("- rest"), Ok((" rest", Token::Sub)));
assert_eq!(parse_operator("* rest"), Ok((" rest", Token::Mul)));
assert_eq!(parse_operator("/ rest"), Ok((" rest", Token::Div)));
assert!(parse_operator("% rest").is_err());
}alt combines cleanly with map for parsing into custom types.
Two-Way Choice: When or Makes Sense
use nom::combinator::or;
use nom::bytes::complete::tag;
use nom::character::complete::digit1;
use nom::IResult;
fn main() {
// For simple two-way choices, or is perfectly fine
let string_or_number = or(
tag("\""),
digit1
);
// This is clearer than alt for just two options
let result1 = string_or_number("\"hello");
println!("{:?}", result1); // Ok(("hello", "\""))
let result2 = string_or_number("12345");
println!("{:?}", result2); // Ok(("", "12345"))
// or reads naturally for binary choices:
// "try this OR that"
}or is appropriate for simple two-way choices where clarity matters.
Nested Parsers with alt
use nom::branch::alt;
use nom::bytes::complete::{tag, take_while};
use nom::character::complete::{digit1, alpha1};
use nom::sequence::delimited;
use nom::IResult;
#[derive(Debug)]
enum Literal {
String(String),
Number(i64),
Boolean(bool),
}
fn parse_literal(input: &str) -> IResult<&str, Literal> {
alt((
// String literal
|i| {
delimited(tag("\""), take_while(|c| c != '"'), tag("\""))(i)
.map(|(r, s)| (r, Literal::String(s.to_string())))
},
// Boolean literals
|i| tag("true")(i).map(|(r, _)| (r, Literal::Boolean(true))),
|i| tag("false")(i).map(|(r, _)| (r, Literal::Boolean(false))),
// Number literal
|i| digit1(i).map(|(r, n)| (r, Literal::Number(n.parse().unwrap()))),
))(input)
}
fn main() {
assert!(matches!(parse_literal("\"hello\""), Ok((_, Literal::String(_)))));
assert!(matches!(parse_literal("42"), Ok((_, Literal::Number(42)))));
assert!(matches!(parse_literal("true"), Ok((_, Literal::Boolean(true)))));
}alt handles complex nested parsers with different return types.
Error Accumulation Behavior
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::error::{VerboseError, VerboseErrorKind};
use nom::IResult;
fn main() {
// With VerboseError, alt accumulates context from all failures
let parser = alt((
tag("hello"),
tag("world"),
tag("foo"),
));
let input = "unknown";
// Error will show all attempted alternatives
let result: IResult<&str, &str, VerboseError<&str>> = parser(input);
match result {
Err(nom::Err::Error(e)) => {
println!("Error context accumulated from all alternatives");
// VerboseError contains errors from each parser tried
}
_ => {}
}
}With verbose error types, alt can provide richer error messages from all attempts.
Practical Example: Expression Parser
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::{digit1, space0};
use nom::sequence::{pair, delimited};
use nom::combinator::map;
use nom::IResult;
#[derive(Debug)]
enum Expr {
Number(i64),
Add(Box<Expr>, Box<Expr>),
Sub(Box<Expr>, Box<Expr>),
Paren(Box<Expr>),
}
fn parse_number(input: &str) -> IResult<&str, Expr> {
map(digit1, |s: &str| Expr::Number(s.parse().unwrap()))(input)
}
fn parse_parens(input: &str) -> IResult<&str, Expr> {
map(
delimited(tag("("), parse_expr, tag(")")),
|e| Expr::Paren(Box::new(e))
)(input)
}
fn parse_primary(input: &str) -> IResult<&str, Expr> {
alt((
parse_number,
parse_parens,
))(input)
}
fn parse_expr(input: &str) -> IResult<&str, Expr> {
// Simplified - just showing alt usage
parse_primary(input)
}
fn main() {
let result = parse_primary("42");
println!("{:?}", result); // Ok(("", Number(42)))
let result2 = parse_primary("(1)");
println!("{:?}", result2); // Ok(("", Paren(Number(1))))
}alt is essential for expression parsers with multiple alternatives.
Comparison Summary
use nom::branch::alt;
use nom::combinator::or;
use nom::bytes::complete::tag;
fn main() {
// Summary of differences:
// alt:
// - Takes tuple of parsers (2 or more)
// - Optimized for multiple alternatives
// - Accumulates errors from all parsers
// - Clean syntax for many alternatives
// - Preferred for 3+ alternatives
let alt_parser = alt((
tag("a"),
tag("b"),
tag("c"),
tag("d"),
));
// or:
// - Takes exactly 2 parsers
// - Binary choice
// - Returns error from last parser
// - Can chain but creates nesting
// - Fine for simple two-way choices
let or_parser = or(tag("a"), tag("b"));
// For two parsers: both work, or is slightly simpler
// For 3+ parsers: alt is cleaner and more efficient
// Chained or (avoid for many alternatives):
let chain = or(tag("a"), or(tag("b"), tag("c")));
// Works but less readable than alt
}The key difference: alt for multiple alternatives, or for binary choices.
Synthesis
Core distinction:
alt: Tuple of parsers, tries each in sequence, returns first successor: Binary combinator, tries first then second
Error handling:
alt: Can accumulate errors from all attempted parsersor: Returns error from the last parser tried- Use
VerboseErrorwithaltfor better messages
Performance:
alt: Optimized for multiple alternatives, single pass structureor: Chaining creates nested calls and error type nesting- For 2 parsers: negligible difference
- For 3+ parsers:
altpreferred
When to use alt:
- Three or more alternatives
- Need error context from all failures
- Parsing keywords, operators, tokens
- Clean code for multiple mapped parsers
When to use or:
- Exactly two alternatives
- Simple binary choice
- Readability: "try this or that" is clear
Common patterns:
alt+map: Transform multiple alternatives into typesaltfor parsing expressions with multiple alternatives- Nested
altfor recursive grammars
Key insight: Both combinators express "try alternatives until one succeeds," but alt generalizes this to any number of parsers while or is strictly binary. The alt combinator is more than syntactic sugarâit's optimized for the multi-alternative case with better error accumulation and cleaner error types. Chaining or works but creates increasingly nested error types (like Result<Result<Result<...>>>>), making error handling complex. Use alt whenever you have more than two alternatives; use or for simple two-way choices where the binary nature reads clearly in context.
