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.