What is the purpose of nom::branch::alt for parsing alternatives with different return types?
nom::branch::alt tries each parser in sequence until one succeeds, enabling you to parse alternatives where each branch can produce different output types through careful type unification or by mapping to a common enum. The combinator is fundamental for choice in parser combinators, implementing ordered choice where the first successful parser wins, and requires all branches to have compatible return types or be mapped to a unified type.
Basic alt Usage
use nom::{
IResult,
branch::alt,
bytes::complete::tag,
};
// alt tries parsers in order, returning first success
fn parse_greeting(input: &str) -> IResult<&str, &str> {
alt((
tag("hello"),
tag("hi"),
tag("hey"),
))(input)
}
fn main() {
assert_eq!(parse_greeting("hello world"), Ok((" world", "hello")));
assert_eq!(parse_greeting("hi there"), Ok((" there", "hi")));
assert_eq!(parse_greeting("hey!"), Ok(("!", "hey")));
// All parsers fail - returns error from last parser
assert!(parse_greeting("goodbye").is_err());
}alt tries each parser in order and returns the first successful result.
Same Return Type with alt
use nom::{
IResult,
branch::alt,
bytes::complete::tag,
};
// All parsers must return the same type
fn parse_keyword(input: &str) -> IResult<&str, &str> {
alt((
tag("if"),
tag("else"),
tag("while"),
tag("for"),
))(input)
}
// This works because all branches return &str
fn parse_multiple(input: &str) -> IResult<&str, Vec<&str>> {
let mut result = Vec::new();
let mut remaining = input;
loop {
remaining = nom::character::complete::space0(remaining)?.0;
match parse_keyword(remaining) {
Ok((rem, kw)) => {
result.push(kw);
remaining = rem;
}
Err(_) => break,
}
}
Ok((remaining, result))
}All branches in alt must return the same output type.
Different Return Types: The Problem
use nom::{
IResult,
branch::alt,
bytes::complete::tag,
character::complete::digit1,
};
// THIS DOESN'T COMPILE - different return types
fn parse_different_types(input: &str) -> IResult<&str, ?> {
// Error: mismatched types
// tag returns &str, digit1 returns &str, but mapped versions differ
alt((
tag("hello"), // returns &str
digit1.map(|s: &str| s.parse::<u32>().unwrap()), // returns u32
))(input)
}alt requires all branches to return the same typeâthis won't compile.
Solution: Map to Common Enum
use nom::{
IResult,
branch::alt,
bytes::complete::tag,
character::complete::{digit1, alpha1},
};
// Define a common enum for all alternatives
#[derive(Debug, PartialEq)]
enum Token {
Word(String),
Number(u32),
Keyword(String),
}
fn parse_token(input: &str) -> IResult<&str, Token> {
alt((
// Map each parser to the same enum type
tag("if").map(|s: &str| Token::Keyword(s.to_string())),
tag("else").map(|s: &str| Token::Keyword(s.to_string())),
digit1.map(|s: &str| Token::Number(s.parse().unwrap())),
alpha1.map(|s: &str| Token::Word(s.to_string())),
))(input)
}
fn main() {
assert_eq!(parse_token("if"), Ok(("", Token::Keyword("if".to_string()))));
assert_eq!(parse_token("123"), Ok(("", Token::Number(123))));
assert_eq!(parse_token("hello"), Ok(("", Token::Word("hello".to_string()))));
}Map each parser to a common enum to unify different return types.
Using value for Simple Alternatives
use nom::{
IResult,
branch::alt,
bytes::complete::tag,
combinator::value,
};
#[derive(Debug, PartialEq)]
enum Operator {
Add,
Sub,
Mul,
Div,
}
fn parse_operator(input: &str) -> IResult<&str, Operator> {
alt((
value(Operator::Add, tag("+")),
value(Operator::Sub, tag("-")),
value(Operator::Mul, tag("*")),
value(Operator::Div, tag("/")),
))(input)
}
fn main() {
assert_eq!(parse_operator("+"), Ok(("", Operator::Add)));
assert_eq!(parse_operator("-"), Ok(("", Operator::Sub)));
assert_eq!(parse_operator("*"), Ok(("", Operator::Mul)));
assert_eq!(parse_operator("/"), Ok(("", Operator::Div)));
}value combinator maps successful parse to a constant value.
Ordered Choice Semantics
use nom::{
IResult,
branch::alt,
bytes::complete::tag,
};
// alt uses ordered choice - first match wins
fn parse_conflicting(input: &str) -> IResult<&str, &str> {
alt((
tag("hello"),
tag("hell"), // Never matches - shadowed by "hello"
tag("heaven"), // Never matches "he" - shadowed
))(input)
}
fn main() {
// "hello" matches first parser
assert_eq!(parse_conflicting("hello"), Ok(("", "hello")));
// "hell" would match "hello" partially, but...
// nom backtracks correctly
assert!(parse_conflicting("hell").is_err()); // Actually fails - need longer match
// Order matters for overlapping patterns
let result = alt((
tag("hell"), // This shadows "hello"
tag("hello"),
))("hello");
// Only matches "hell", leaving "o" unconsumed
assert_eq!(result, Ok(("o", "hell")));
}
// Correct ordering for prefixes:
fn parse_prefixes_correct(input: &str) -> IResult<&str, &str> {
alt((
tag("hello"), // Longest first
tag("hell"),
tag("he"),
))(input)
}Order mattersâput longer matches first when patterns overlap.
Backtracking Behavior
use nom::{
IResult,
branch::alt,
bytes::complete::tag,
character::complete::alpha1,
combinator::recognize,
sequence::pair,
};
// alt backtracks on failure
fn parse_backtrack(input: &str) -> IResult<&str, &str> {
alt((
// First parser: requires "hello" followed by letters
pair(tag("hello"), alpha1).recognize(),
// Second parser: just "hello"
tag("hello"),
))(input)
}
fn main() {
// Input: "hello123"
// First parser: "hello" matches, but "123" fails alpha1
// Backtracks to second parser: "hello" matches
let result = parse_backtrack("hello123");
assert_eq!(result, Ok(("123", "hello")));
// Input: "helloworld"
// First parser: "hello" matches, "world" matches alpha1
// Success - second parser never tried
let result = parse_backtrack("helloworld");
assert_eq!(result, Ok(("", "helloworld")));
}alt backtracks when a parser fails, trying the next alternative.
Nested alt for Complex Choices
use nom::{
IResult,
branch::alt,
bytes::complete::tag,
};
// Nested alt for grouping alternatives
fn parse_expression(input: &str) -> IResult<&str, &str> {
alt((
// Arithmetic operators
alt((
tag("+"),
tag("-"),
tag("*"),
tag("/"),
)),
// Comparison operators
alt((
tag("=="),
tag("!="),
tag("<="),
tag(">="),
tag("<"),
tag(">"),
)),
))(input)
}
fn main() {
assert_eq!(parse_expression("+"), Ok(("", "+")));
assert_eq!(parse_expression("=="), Ok(("", "==")));
assert_eq!(parse_expression("<="), Ok(("", "<="))); // Not just "<"
}Nest alt calls to organize groups of alternatives.
alt with Different Parser Types
use nom::{
IResult,
branch::alt,
bytes::complete::tag,
character::complete::{digit1, alpha1},
combinator::map,
};
// Mix different parser types with unified output
#[derive(Debug)]
enum Value {
String(String),
Number(i64),
Boolean(bool),
}
fn parse_value(input: &str) -> IResult<&str, Value> {
alt((
map(tag("true"), |_| Value::Boolean(true)),
map(tag("false"), |_| Value::Boolean(false)),
map(digit1, |s: &str| Value::Number(s.parse().unwrap())),
map(alpha1, |s: &str| Value::String(s.to_string())),
))(input)
}
fn main() {
assert!(matches!(parse_value("true"), Ok(("", Value::Boolean(true)))));
assert!(matches!(parse_value("42"), Ok(("", Value::Number(42)))));
assert!(matches!(parse_value("hello"), Ok(("", Value::String(_)))));
}Different underlying parsers can produce the same output type.
Error Handling in alt
use nom::{
IResult,
branch::alt,
bytes::complete::tag,
error::{Error, ErrorKind, ParseError},
};
// alt returns the error from the last parser when all fail
fn parse_with_error(input: &str) -> IResult<&str, &str> {
alt((
tag("first"),
tag("second"),
tag("third"),
))(input)
}
fn main() {
let result = parse_with_error("unknown");
// Error comes from the last parser ("third")
assert!(result.is_err());
}
// For better errors, use context
use nom::combinator::context;
fn parse_with_context(input: &str) -> IResult<&str, &str> {
alt((
context("first keyword", tag("first")),
context("second keyword", tag("second")),
context("third keyword", tag("third")),
))(input)
}When all alternatives fail, alt returns the error from the last parser.
alt vs permutation
use nom::{
IResult,
branch::{alt, permutation},
bytes::complete::tag,
};
// alt: ordered choice - first match wins
fn parse_alt(input: &str) -> IResult<&str, &str> {
alt((tag("a"), tag("b"), tag("c")))(input)
}
// permutation: all must match in any order
fn parse_permutation(input: &str) -> IResult<&str, (&str, &str, &str)> {
permutation((
tag("a"),
tag("b"),
tag("c"),
))(input)
}
fn main() {
// alt returns first match
assert_eq!(parse_alt("abc"), Ok(("bc", "a")));
// permutation matches all in any order
assert_eq!(parse_permutation("cba"), Ok(("", ("c", "b", "a"))));
assert_eq!(parse_permutation("abc"), Ok(("", ("a", "b", "c"))));
// Use alt for choice, permutation for required elements in any order
}Use alt for choice; use permutation for required elements in any order.
Performance Considerations
use nom::{
IResult,
branch::alt,
bytes::complete::tag,
};
// Order by likelihood for better performance
fn parse_keywords(input: &str) -> IResult<&str, &str> {
// Put most common keywords first
alt((
tag("let"), // Most common
tag("fn"), // Common
tag("if"), // Common
tag("else"), // Less common
tag("while"), // Less common
tag("for"), // Less common
))(input)
}
// For large alternatives, consider a trie or HashMap
fn parse_with_hashmap(input: &str) -> IResult<&str, &str> {
// For many alternatives, a HashMap lookup may be faster
// But alt is simpler and often sufficient
alt((
tag("keyword"),
// ...
))(input)
}Order alternatives by likelihoodâcommon patterns first improves average performance.
Tuple Syntax in alt
use nom::{
IResult,
branch::alt,
bytes::complete::tag,
};
// alt accepts tuples of parsers
fn parse_tuple(input: &str) -> IResult<&str, &str> {
alt((
tag("a"),
tag("b"),
tag("c"),
tag("d"),
tag("e"),
))(input)
}
// The tuple can be any length
fn parse_long(input: &str) -> IResult<&str, &str> {
alt((
tag("first"),
tag("second"),
tag("third"),
tag("fourth"),
tag("fifth"),
tag("sixth"),
tag("seventh"),
tag("eighth"),
tag("ninth"),
tag("tenth"),
))(input)
}alt accepts tuples of any length for convenience.
Practical Example: Tokenizer
use nom::{
IResult,
branch::alt,
bytes::complete::tag,
character::complete::{digit1, alpha1, multispace1},
combinator::{map, value, recognize},
sequence::pair,
};
#[derive(Debug, PartialEq)]
enum Token {
// Keywords
Let,
Fn,
If,
Else,
// Operators
Plus,
Minus,
Equals,
// Literals
Number(i64),
Ident(String),
// Whitespace (usually skipped)
Whitespace,
}
fn tokenize(input: &str) -> IResult<&str, Token> {
alt((
// Keywords
value(Token::Let, tag("let")),
value(Token::Fn, tag("fn")),
value(Token::If, tag("if")),
value(Token::Else, tag("else")),
// Operators
value(Token::Plus, tag("+")),
value(Token::Minus, tag("-")),
value(Token::Equals, tag("==")),
// Literals
map(digit1, |s: &str| Token::Number(s.parse().unwrap())),
// Identifiers (after keywords so keywords match first)
map(alpha1, |s: &str| Token::Ident(s.to_string())),
// Whitespace
value(Token::Whitespace, multispace1),
))(input)
}
fn main() {
let input = "let x = 42";
let mut remaining = input;
while !remaining.is_empty() {
match tokenize(remaining) {
Ok((rem, token)) => {
if token != Token::Whitespace {
println!("{:?}", token);
}
remaining = rem;
}
Err(_) => break,
}
}
}Build tokenizers by combining alt with value and map.
Returning Different Types with Either
use nom::{
IResult,
branch::alt,
bytes::complete::tag,
character::complete::{digit1, alpha1},
};
// Use Either for two-branch alternatives
type Either<T, U> = Result<T, U>;
fn parse_either(input: &str) -> IResult<&str, Result<u32, &str>> {
alt((
// Return Ok(u32) for numbers
digit1.map(|s: &str| Ok(s.parse().unwrap())),
// Return Err(&str) for words
alpha1.map(Err),
))(input)
}
// Or use a custom enum
#[derive(Debug)]
enum Parsed {
Num(u32),
Word(String),
}
fn parse_custom(input: &str) -> IResult<&str, Parsed> {
alt((
digit1.map(|s: &str| Parsed::Num(s.parse().unwrap())),
alpha1.map(|s: &str| Parsed::Word(s.to_string())),
))(input)
}Use enums or Either types to unify different return types.
Synthesis
Quick reference:
| Aspect | Behavior |
|---|---|
| Order | First matching parser wins |
| Backtracking | Yesâtries next parser on failure |
| Return type | All branches must unify to same type |
| Error | Returns error from last parser on total failure |
Type unification strategies:
use nom::{IResult, branch::alt, bytes::complete::tag, combinator::value};
// Strategy 1: Same return type (all &str)
fn same_type(input: &str) -> IResult<&str, &str> {
alt((tag("a"), tag("b"), tag("c")))(input)
}
// Strategy 2: Enum with map/value
#[derive(Debug)]
enum Token { A, B, C }
fn enum_type(input: &str) -> IResult<&str, Token> {
alt((
value(Token::A, tag("a")),
value(Token::B, tag("b")),
value(Token::C, tag("c")),
))(input)
}
// Strategy 3: Map to common type
fn common_type(input: &str) -> IResult<&str, String> {
alt((
tag("a").map(|s| s.to_uppercase()),
tag("b").map(|s| s.to_uppercase()),
tag("c").map(|s| s.to_uppercase()),
))(input)
}Key insight: nom::branch::alt implements ordered choice in parser combinatorsâtry each parser in sequence and return the first successful result. The fundamental constraint is that all branches must return the same output type, which you achieve by mapping each parser to a common enum or type. The ordering matters because alt uses first-match-wins semantics: if you have alt((tag("hell"), tag("hello"))), the input "hello" will match "hell" and leave "o" unconsumed. Put longer or more specific patterns before shorter prefixes. When all parsers fail, alt returns the error from the last parserâthe backtracking is complete, and the error reflects the final alternative tried. For complex tokenizers, combine alt with value for keyword mapping, map for type conversion, and context for better error messages. The combinator is the foundation of choice in nom parsers, enabling you to express "this OR that OR that" patterns cleanly while requiring explicit type unification to ensure type safety.
