How does nom::sequence::tuple combine multiple parsers into a single parser returning a tuple of results?
nom::sequence::tuple takes multiple parsers and combines them into a single parser that applies each one in sequence, returning their results as a tuple. If any parser fails, the entire tuple parser fails immediately with the first error, providing short-circuit behavior for parsing sequences.
Basic Tuple Parser Usage
use nom::{
sequence::tuple,
character::complete::{alpha1, digit1, space1},
IResult,
};
fn parse_user() -> IResult<&str, (&str, &str, &str)> {
// Apply three parsers in sequence
tuple((alpha1, space1, digit1))(input)
}
fn main() {
let input = "john 42";
let result = tuple((alpha1, space1, digit1))(input);
// Result: Ok(("", ("john", " ", "42")))
// Input consumed: all
// Output: tuple of each parser's result
}tuple applies parsers left-to-right, collecting results into a tuple.
Type Signature and Return Value
use nom::sequence::tuple;
use nom::IResult;
// tuple takes a tuple of parsers and returns a parser producing a tuple of results
// Signature (simplified):
// fn tuple<I, O1, O2, ..., E, P1, P2, ...>(parsers: (P1, P2, ...))
// -> impl Fn(I) -> IResult<I, (O1, O2, ...)>
// where
// P1: Fn(I) -> IResult<I, O1, E>,
// P2: Fn(I) -> IResult<I, O2, E>,
// ...
fn type_demonstration() {
let parser = tuple((
|i: &str| Ok((i, 1i32)), // Parser 1 returns i32
|i: &str| Ok((i, "hello")), // Parser 2 returns &str
|i: &str| Ok((i, 2.0f64)), // Parser 3 returns f64
));
// Result type: (&str, (i32, &str, f64))
// First element is remaining input
// Second element is tuple of all parser results
}The output tuple type matches the input parser tuple structure.
Short-Circuit Error Behavior
use nom::{
sequence::tuple,
character::complete::{alpha1, digit1},
error::{Error, ErrorKind},
IResult,
};
fn error_handling() {
// If any parser fails, the entire tuple fails immediately
let result = tuple((
alpha1, // Succeeds: "hello"
digit1, // Fails: no digits after "hello world"
))("hello world");
// Result: Err(Error { input: " world", code: Digit })
// Parser stops at first failure, doesn't continue
// Compare with applying parsers separately:
// Each parser would process remaining input
}Failures short-circuit; remaining parsers aren't called after an error.
Combining Different Parser Types
use nom::{
sequence::tuple,
character::complete::{alpha1, digit1, char, space1},
bytes::complete::tag,
number::complete::u32,
IResult,
};
fn mixed_parsers(input: &str) -> IResult<&str, (&str, char, u32, &str)> {
tuple((
alpha1, // Parse alphabetic characters
char(' '), // Parse specific character
u32, // Parse unsigned integer
tag!("end"), // Parse literal string
))(input)
}
fn main() {
let result = mixed_parsers("abc 123end");
// Ok(("", ("abc", ' ', 123, "end")))
}tuple works with any parsers that share the same input and error types.
Tuple Sizes and Variadic Behavior
use nom::sequence::tuple;
use nom::character::complete::anychar;
fn tuple_sizes() {
// 2-tuple
let _: IResult<&str, (char, char)> = tuple((anychar, anychar))("ab");
// 3-tuple
let _: IResult<&str, (char, char, char)> = tuple((anychar, anychar, anychar))("abc");
// 4-tuple
let _: IResult<&str, (char, char, char, char)> =
tuple((anychar, anychar, anychar, anychar))("abcd");
// nom supports tuples up to 21 elements
// For more parsers, use nested tuples or custom structs
}tuple supports various sizes; nom implements for tuples from 0 to 21 elements.
Working with the Results
use nom::{
sequence::tuple,
character::complete::{alpha1, digit1},
combinator::map,
IResult,
};
fn process_results(input: &str) -> IResult<&str, String> {
// Use map to transform tuple results
let (remaining, (name, id)) = tuple((alpha1, digit1))(input)?;
Ok((remaining, format!("{}-{}", name, id)))
}
fn map_pattern(input: &str) -> IResult<&str, (String, u32)> {
// Destructure in map closure
map(
tuple((alpha1, digit1)),
|(name, id): (&str, &str)| (name.to_uppercase(), id.parse().unwrap())
)(input)
}Pattern matching in closures makes tuple results easy to work with.
Comparison with Other Sequence Combinators
use nom::{
sequence::{tuple, preceded, delimited, pair, separated_pair},
character::complete::{alpha1, digit1, char},
IResult,
};
fn sequence_comparison() {
let input = "abc123";
// tuple: Apply all, return all results
let _: IResult<&str, (&str, &str)> = tuple((alpha1, digit1))(input);
// pair: Apply two, return both (special case of tuple)
let _: IResult<&str, (&str, &str)> = pair(alpha1, digit1)(input);
// preceded: Apply two, return second
let _: IResult<&str, &str> = preceded(alpha1, digit1)(input);
// delimited: Apply three, return middle
let input2 = "(abc)";
let _: IResult<&str, &str> = delimited(char('('), alpha1, char(')'))(input2);
// separated_pair: Apply three, return first and third
let input3 = "abc,123";
let _: IResult<&str, (&str, &str)> = separated_pair(alpha1, char(','), digit1)(input3);
}tuple returns all results; other combinators may discard some.
Nested Tuples
use nom::{
sequence::tuple,
character::complete::{alpha1, digit1, space1, char},
IResult,
};
fn nested_tuples(input: &str) -> IResult<&str, ((&str, &str), &str)> {
// Nest tuples for hierarchical grouping
tuple((
tuple((alpha1, digit1)), // Inner tuple: ("name", "1")
space1,
))(input)
}
fn main() {
let result = nested_tuples("abc123 ");
// Ok(("", (("abc", "123"), " ")))
}Nested tuples create hierarchical result structures.
Building Complex Parsers
use nom::{
sequence::tuple,
character::complete::{alpha1, digit1, char, space0, space1},
bytes::complete::tag,
combinator::map,
IResult,
};
#[derive(Debug)]
struct HttpRequest {
method: String,
path: String,
version: String,
}
fn parse_http_request(input: &str) -> IResult<&str, HttpRequest> {
map(
tuple((
alpha1, // Method
space1, // Space
alpha1, // Path (simplified)
space1, // Space
tag!("HTTP/1.1"), // Version
)),
|(method, _, path, _, version)| HttpRequest {
method: method.to_string(),
path: path.to_string(),
version: version.to_string(),
}
)(input)
}
fn main() {
let result = parse_http_request("GET index HTTP/1.1");
// Ok(("", HttpRequest { method: "GET", path: "index", version: "HTTP/1.1" }))
}Complex structures are built by combining tuples with map.
Zero-Element Tuple
use nom::sequence::tuple;
fn empty_tuple(input: &str) -> IResult<&str, ()> {
// Empty tuple always succeeds, returns unit
tuple(())(input)
}
fn main() {
let result = tuple(())("any input");
// Ok(("any input", ()))
// No parsing done, returns unit
}An empty tuple is a no-op parser that always succeeds.
Single-Element Tuple
use nom::{sequence::tuple, character::complete::alpha1, IResult};
fn single_element(input: &str) -> IResult<&str, (&str,)> {
// Single-element tuple returns single-element tuple
tuple((alpha1,))(input)
}
fn main() {
let result = tuple((alpha1,))("hello");
// Ok(("", ("hello",)))
// Note: result is a 1-tuple, not just &str
}Single-element tuples still return tuples; use the parser directly for single values.
Optional Parsers in Tuples
use nom::{
sequence::tuple,
character::complete::{alpha1, space0},
combinator::opt,
IResult,
};
fn with_optional(input: &str) -> IResult<&str, (&str, Option<&str>)> {
tuple((
alpha1,
opt(alpha1), // Optional second word
))(input)
}
fn main() {
let result1 = with_optional("hello");
// Ok(("", ("hello", None)))
let result2 = with_optional("helloworld");
// Ok(("", ("helloworld", None))) // alpha1 consumes all
}Combine tuple with opt for optional components.
Many Parsers in Tuples
use nom::{
sequence::tuple,
character::complete::char,
multi::many0,
combinator::map,
IResult,
};
fn with_repetition(input: &str) -> IResult<&str, (char, Vec<char>)> {
tuple((
char('a'),
many0(char('b')), // Collect multiple 'b's
))(input)
}
fn main() {
let result = with_repetition("abbb");
// Ok(("", ('a', vec
!['b', 'b', 'b'])))
}Tuples can include repetition combinators like many0 or many1.
Error Type Propagation
use nom::{
sequence::tuple,
character::complete::digit1,
error::{Error, ParseError},
IResult,
};
fn error_propagation(input: &str) -> IResult<&str, (&str, &str)> {
// All parsers must have compatible error types
tuple((
|i: &str| -> IResult<&str, &str> { Ok((i, "prefix")) },
digit1,
))(input)
}
// Custom error types require all parsers to use compatible errors
fn custom_errors<I, E: ParseError<I>>(input: I) -> IResult<I, (&str, &str), E> {
tuple((
|i: I| Ok((i, "a")),
|i: I| Ok((i, "b")),
))(input)
}All parsers in a tuple must use compatible error types.
Backtracking Behavior
use nom::{
sequence::tuple,
character::complete::{alpha1, digit1},
IResult,
};
fn backtracking(input: &str) {
// tuple does NOT backtrack on success
// If first parser succeeds but second fails, input is partially consumed
let result = tuple((
alpha1, // Succeeds, consumes "hello"
digit1, // Fails on " world"
))("hello world");
// Result: Err(Error { input: " world", code: Digit })
// Input "hello" was consumed by first parser
// For backtracking, use nom::combinator::consumed or other patterns
}Successful parsers consume input; failed subsequent parsers don't restore it.
Complete Parsing Example
use nom::{
sequence::tuple,
character::complete::{alpha1, digit1, char, space0, space1, multispace0},
bytes::complete::tag,
combinator::map,
multi::separated_list0,
IResult,
};
#[derive(Debug, PartialEq)]
struct Config {
name: String,
value: u32,
items: Vec<String>,
}
fn parse_config(input: &str) -> IResult<&str, Config> {
map(
tuple((
alpha1, // name
space1, // separator
digit1, // value (as string)
space0, // optional space
char(':'), // colon
space0, // optional space
separated_list0(char(','), alpha1) // items
)),
|(name, _, value, _, _, _, items)| Config {
name: name.to_string(),
value: value.parse().unwrap(),
items: items.into_iter().map(|s| s.to_string()).collect(),
}
)(input)
}
fn main() {
let input = "config 42: alpha,beta,gamma";
let result = parse_config(input);
assert_eq!(result, Ok((
"",
Config {
name: "config".to_string(),
value: 42,
items: vec
!["alpha".to_string(), "beta".to_string(), "gamma".to_string()],
}
)));
}Comparison with Do-Notation Style
use nom::{
sequence::tuple,
character::complete::{alpha1, digit1},
IResult,
};
// Using tuple combinator
fn with_tuple(input: &str) -> IResult<&str, (&str, &str)> {
tuple((alpha1, digit1))(input)
}
// Using explicit sequencing (similar effect)
fn with_explicit(input: &str) -> IResult<&str, (&str, &str)> {
let (input, first) = alpha1(input)?;
let (input, second) = digit1(input)?;
Ok((input, (first, second)))
}
// Both approaches are equivalent
// tuple is more concise for simple sequences
// Explicit sequencing allows more complex logic between parserstuple is syntactic sugar for sequential parser application.
Practical Pattern: Parsing Records
use nom::{
sequence::tuple,
character::complete::{alpha1, digit1, char, space0, line_ending},
bytes::complete::take_until,
combinator::map,
multi::many1,
IResult,
};
#[derive(Debug)]
struct Record {
id: u32,
name: String,
description: String,
}
fn parse_record(input: &str) -> IResult<&str, Record> {
map(
tuple((
digit1,
char('\t'),
alpha1,
char('\t'),
take_until("\n"),
line_ending,
)),
|(id, _, name, _, desc, _)| Record {
id: id.parse().unwrap(),
name: name.to_string(),
description: desc.to_string(),
}
)(input)
}
fn parse_records(input: &str) -> IResult<&str, Vec<Record>> {
many1(parse_record)(input)
}
fn main() {
let input = "1\talice\tfirst user\n2\tbob\tsecond user\n";
let records = parse_records(input);
// Parses both records into a Vec<Record>
}Summary Table
fn summary_table() {
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
// ā Combinator ā Input Parsers ā Result Type ā Behavior ā
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
// ā tuple((A, B, C)) ā 3 parsers ā (OA, OB, OC) ā Keep all ā
// ā pair(A, B) ā 2 parsers ā (OA, OB) ā Keep all ā
// ā preceded(A, B) ā 2 parsers ā OB ā Discard A ā
// ā delimited(A, B, C) ā 3 parsers ā OB ā Discard A,Cā
// ā separated_pair(A,B,C)ā 3 parsers ā (OA, OC) ā Discard B ā
// ā (tuple(())) ā 0 parsers ā () ā No-op ā
// ā (tuple((A,))) ā 1 parser ā (OA,) ā 1-tuple ā
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
}Summary
fn summary() {
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
// ā Aspect ā Behavior ā
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
// ā Ordering ā Parsers applied left-to-right ā
// ā Result type ā Tuple of individual parser results ā
// ā Error handling ā Short-circuits on first failure ā
// ā Input consumption ā Sequential, no backtracking on success ā
// ā Maximum size ā Up to 21 parsers in one tuple ā
// ā Empty tuple ā Always succeeds, returns () ā
// ā Nesting ā Supports nested tuples ā
// ā Error types ā All parsers must share error type ā
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
// Key points:
// 1. tuple applies multiple parsers in sequence
// 2. Results are collected into a tuple matching parser order
// 3. Short-circuits on first error, returns that error
// 4. Supports 0-21 parsers directly
// 5. Works with any parsers sharing input and error types
// 6. Use map to transform tuple into structured data
// 7. Nested tuples create hierarchical structures
// 8. Combine with opt, many0, etc. for optional/repeated elements
// 9. pair is a special case of tuple for two parsers
// 10. More concise than explicit sequencing for simple cases
}Key insight: nom::sequence::tuple is the foundational combinator for sequencing multiple parsers in nom. It represents the concept of "apply these parsers in order and collect all results." Unlike preceded or delimited which discard certain results, tuple preserves every parser's output in the result tuple. The key behaviors are: (1) left-to-right application, (2) immediate short-circuit on failure with no backtracking of consumed input, and (3) result collection into a tuple that mirrors the parser structure. Use tuple when you need all parsed values; use specialized combinators like preceded when you want to discard prefix values. For complex structures, combine tuple with map to transform the tuple into domain types. The 21-element limit covers most practical cases; for more complex sequences, use nested tuples or custom parser functions with explicit sequencing.
