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.