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 success
  • or: Binary combinator, tries first then second

Error handling:

  • alt: Can accumulate errors from all attempted parsers
  • or: Returns error from the last parser tried
  • Use VerboseError with alt for better messages

Performance:

  • alt: Optimized for multiple alternatives, single pass structure
  • or: Chaining creates nested calls and error type nesting
  • For 2 parsers: negligible difference
  • For 3+ parsers: alt preferred

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 types
  • alt for parsing expressions with multiple alternatives
  • Nested alt for 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.