How does nom::sequence::tuple combine multiple parsers into a single output?
nom::sequence::tuple is a parser combinator that takes a tuple of parsers as input and returns their results as a tuple of outputs, applying each parser sequentially to the same input and succeeding only when all parsers succeed in order. This enables parsing multiple consecutive patterns and collecting all results together, making it fundamental for structured parsing where you need to capture several related pieces of data from input.
Understanding Parser Combinators in nom
use nom::{
IResult,
bytes::complete::{tag, take_while},
character::complete::{digit1, alpha1},
sequence::tuple,
};
// nom parsers have the signature: Input -> IResult<Input, Output>
// IResult<Input, Output> = Result<(Input, Output), Err>
//
// Parser combinators combine parsers to build complex parsers from simple ones
fn parser_basics() {
// Simple parsers parse specific patterns
let input = "hello world";
// tag matches a literal string
let result: IResult<&str, &str> = tag("hello")(input);
// Ok((" world", "hello"))
// The result is: (remaining_input, matched_output)
}Parser combinators build complex parsers by combining simpler ones.
The tuple Combinator
use nom::{
IResult,
bytes::complete::tag,
sequence::tuple,
};
fn tuple_basic() {
// tuple takes a tuple of parsers and returns a tuple of results
let input = "hello world";
// Apply two parsers in sequence
let result: IResult<&str, (&str, &str)> = tuple((
tag("hello"),
tag(" world"),
))(input);
// Result: Ok(("", ("hello", " world")))
// - Empty remaining input
// - Tuple of both matches
let (remaining, (first, second)) = result.unwrap();
assert_eq!(remaining, "");
assert_eq!(first, "hello");
assert_eq!(second, " world");
}tuple combines multiple parsers into one, returning a tuple of all results.
How tuple Processes Input
use nom::{
IResult,
bytes::complete::tag,
character::complete::{alpha1, digit1},
sequence::tuple,
};
fn how_tuple_works() {
// tuple processes parsers in order:
// 1. Apply first parser to input
// 2. Apply second parser to remaining input from first
// 3. Apply third parser to remaining input from second
// 4. Continue until all parsers applied
// 5. Return tuple of all results
let input = "abc123xyz";
let result = tuple((
alpha1, // Parse letters
digit1, // Parse digits
alpha1, // Parse letters
))(input);
// Processing:
// 1. alpha1("abc123xyz") -> Ok(("123xyz", "abc"))
// 2. digit1("123xyz") -> Ok(("xyz", "123"))
// 3. alpha1("xyz") -> Ok(("", "xyz"))
// 4. Return Ok(("", ("abc", "123", "xyz")))
let (remaining, (letters1, numbers, letters2)) = result.unwrap();
assert_eq!(remaining, "");
assert_eq!(letters1, "abc");
assert_eq!(numbers, "123");
assert_eq!(letters2, "xyz");
}Each parser consumes input sequentially; remaining input flows to the next parser.
Matching Different Types
use nom::{
IResult,
bytes::complete::tag,
character::complete::{digit1, space1, alpha1, char},
sequence::tuple,
};
fn different_output_types() {
// Each parser can return a different type
// tuple preserves those types in the result tuple
fn parse_record(input: &str) -> IResult<&str, (&str, char, &str)> {
tuple((
alpha1, // Returns &str
char(':'), // Returns char
digit1, // Returns &str
))(input)
}
let result = parse_record("count:42");
let (_, (name, colon, value)) = result.unwrap();
assert_eq!(name, "count");
assert_eq!(colon, ':');
assert_eq!(value, "42");
// The result tuple has the exact types from each parser
}The output tuple preserves the exact types from each parser.
Parsing Structured Data
use nom::{
IResult,
bytes::complete::tag,
character::complete::{digit1, space0, alpha1},
sequence::tuple,
};
#[derive(Debug, PartialEq)]
struct Person {
name: String,
age: u32,
}
fn parse_person(input: &str) -> IResult<&str, Person> {
let (remaining, (name, _, age)) = tuple((
alpha1,
space0,
digit1,
))(input)?;
Ok((remaining, Person {
name: name.to_string(),
age: age.parse().unwrap(),
}))
}
fn structured_parsing() {
let result = parse_person("Alice 30").unwrap();
assert_eq!(result.1, Person { name: "Alice".to_string(), age: 30 });
// tuple helps collect multiple fields, then map to struct
}tuple is commonly used to parse multiple fields that form a struct.
Handling Optional Fields
use nom::{
IResult,
bytes::complete::tag,
character::complete::{alpha1, digit1},
sequence::tuple,
combinator::opt,
};
fn optional_in_tuple() {
// Use opt() to make parsers optional within tuple
fn parse_with_optional(input: &str) -> IResult<&str, (&str, Option<&str>, &str)> {
tuple((
alpha1,
opt(digit1), // Optional digits
alpha1,
))(input)
}
// With optional field present
let result1 = parse_with_optional("abc123xyz");
let (_, (a, opt, b)) = result1.unwrap();
assert_eq!(opt, Some("123"));
// Without optional field
let result2 = parse_with_optional("abcxyz");
let (_, (a, opt, b)) = result2.unwrap();
assert_eq!(opt, None);
}Wrap parsers with opt() to make them optional within the tuple.
Error Handling
use nom::{
IResult,
bytes::complete::tag,
character::complete::alpha1,
sequence::tuple,
error::{Error, ErrorKind},
};
fn error_handling() {
// tuple fails if ANY parser fails
// Returns error from the failing parser
let result: IResult<&str, (&str, &str)> = tuple((
alpha1,
tag("world"),
))("hello there");
// Fails because " there" doesn't start with "world"
assert!(result.is_err());
// The error contains information about where it failed
// First parser must succeed for subsequent parsers to run
let result2: IResult<&str, (&str, &str)> = tuple((
tag("x"),
alpha1,
))("abc");
// Fails immediately at first parser
assert!(result2.is_err());
}tuple fails fastβreturns an error as soon as any parser fails.
Combining with Other Combinators
use nom::{
IResult,
bytes::complete::tag,
character::complete::{alpha1, digit1, space0, char},
sequence::{tuple, delimited, preceded},
multi::separated_list1,
};
fn combined_with_other_combinators() {
// tuple works with other sequence combinators
// delimited: parse open, content, close
let parenthesized = delimited(
char('('),
digit1,
char(')'),
);
// preceded: skip prefix, return content
let after_colon = preceded(
char(':'),
alpha1,
);
// Combine in tuple
fn parse_complex(input: &str) -> IResult<&str, (&str, &str)> {
tuple((
parenthesized,
after_colon,
))(input)
}
let result = parse_complex("(123):hello");
let (_, (num, word)) = result.unwrap();
assert_eq!(num, "123");
assert_eq!(word, "hello");
}tuple integrates seamlessly with other nom combinators.
Parsing Lists with tuple
use nom::{
IResult,
bytes::complete::tag,
character::complete::{digit1, char},
sequence::tuple,
multi::separated_list1,
};
fn parsing_csv_line() {
// Parse a CSV-like line using tuple for fields
fn parse_field(input: &str) -> IResult<&str, &str> {
// Field is digits or text until comma
nom::character::complete::alphanumeric1(input)
}
// Parse three fields separated by commas
fn parse_three_fields(input: &str) -> IResult<&str, (&str, &str, &str)> {
tuple((
parse_field,
preceded(char(','), parse_field),
preceded(char(','), parse_field),
))(input)
}
let (_, (f1, f2, f3)) = parse_three_fields("a,b,c").unwrap();
assert_eq!((f1, f2, f3), ("a", "b", "c"));
}tuple helps parse fixed-format data with known field counts.
Variadic Tuple Sizes
use nom::{
IResult,
bytes::complete::tag,
sequence::tuple,
};
fn tuple_sizes() {
// tuple supports 1 to 21 parsers
// Single parser
let single: IResult<&str, (&str,)> = tuple((
tag("a"),
))("abc");
// Two parsers
let double: IResult<&str, (&str, &str)> = tuple((
tag("a"),
tag("b"),
))("abc");
// Three parsers
let triple: IResult<&str, (&str, &str, &str)> = tuple((
tag("a"),
tag("b"),
tag("c"),
))("abc");
// Up to 21 parsers in a single tuple
// Beyond that, nest tuples or use other approaches
}tuple supports up to 21 parsers; nest tuples for more.
Comparison with Alternative Approaches
use nom::{
IResult,
bytes::complete::tag,
character::complete::{alpha1, digit1},
sequence::tuple,
};
fn alternatives_comparison() {
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// β Approach β Pros β Cons β
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
// β tuple(parser1, ...) β Clean, returns tuple β Fixed count (1-21) β
// β Manual sequencing β Flexible β Verbose β
// β do_parse! macro β Named outputs β Legacy, deprecated β
// β flat_map chains β Composable β Complex types β
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// Approach 1: tuple
fn with_tuple(input: &str) -> IResult<&str, (&str, &str)> {
tuple((alpha1, digit1))(input)
}
// Approach 2: Manual sequencing
fn with_manual(input: &str) -> IResult<&str, (&str, &str)> {
let (input, first) = alpha1(input)?;
let (input, second) = digit1(input)?;
Ok((input, (first, second)))
}
// tuple is cleaner for simple cases
// Manual approach gives more control
}tuple is cleaner than manual sequencing; manual gives more control.
Real-World Example: Parsing HTTP Request Line
use nom::{
IResult,
bytes::complete::tag,
character::complete::{alpha1, space1},
sequence::tuple,
};
#[derive(Debug, PartialEq)]
struct HttpRequest {
method: String,
path: String,
version: String,
}
fn parse_http_request(input: &str) -> IResult<&str, HttpRequest> {
// GET /path HTTP/1.1
let (remaining, (method, _, path, _, version)) = tuple((
alpha1, // Method
space1, // Space
tag("/"), // Leading slash (consume but not capture)
// Actually need better path parsing, simplified here
alpha1, // Path (simplified)
space1, // Space
tag("HTTP/1.1"),// Version
))(input)?;
Ok((remaining, HttpRequest {
method: method.to_string(),
path: format!("/{}", path), // Reconstruct with slash
version: "HTTP/1.1".to_string(),
}))
}
fn http_example() {
let result = parse_http_request("GET index HTTP/1.1").unwrap();
assert_eq!(result.1.method, "GET");
}tuple helps parse structured protocols with multiple fields.
Converting Results to Structs
use nom::{
IResult,
bytes::complete::tag,
character::complete::{alpha1, digit1, space0, char},
sequence::tuple,
combinator::map,
};
#[derive(Debug)]
struct User {
name: String,
age: u32,
city: String,
}
fn parse_user(input: &str) -> IResult<&str, User> {
// Use map to convert tuple result to struct
map(
tuple((
alpha1,
preceded(space0, char(',')),
preceded(space0, digit1),
preceded(space0, char(',')),
preceded(space0, alpha1),
)),
|(name, _, age, _, city)| User {
name: name.to_string(),
age: age.parse().unwrap(),
city: city.to_string(),
}
)(input)
}
fn struct_conversion() {
let result = parse_user("Alice, 30, Boston").unwrap();
assert_eq!(result.1.name, "Alice");
assert_eq!(result.1.age, 30);
assert_eq!(result.1.city, "Boston");
}Use map to transform tuple results directly into structs.
Performance Characteristics
use nom::{
IResult,
bytes::complete::tag,
sequence::tuple,
};
fn performance_notes() {
// tuple has minimal overhead:
// 1. Calls each parser in sequence
// 2. Constructs result tuple
// 3. Returns immediately on failure
// No backtracking within tuple
// If second parser fails, first parser's success is discarded
// For complex parsing with backtracking, use
// nom::combinator::alt for alternatives
// tuple is efficient for sequential parsing
// Each parser runs once, in order
}tuple has minimal overhead; no backtracking within the sequence.
Complete Summary
use nom::{
IResult,
bytes::complete::tag,
character::complete::{alpha1, digit1, space0, char},
sequence::tuple,
combinator::map,
};
fn complete_summary() {
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// β Aspect β Behavior β
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
// β Input β Tuple of parsers (up to 21) β
// β Output β Tuple of parser results β
// β Execution β Sequential, left to right β
// β Failure β Fails fast at first failing parser β
// β Error type β Returns error from failing parser β
// β Remaining input β After all parsers have consumed their portions β
// β Composition β Works with any parsers returning IResult β
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// tuple's signature (conceptually):
// fn tuple<I, O>(parsers: (Parser<I, O1>, Parser<I, O2>, ...))
// -> impl Fn(I) -> IResult<I, (O1, O2, ...)>
// Example demonstrating key features:
fn parse_pair(input: &str) -> IResult<&str, (String, u32)> {
map(
tuple((
alpha1,
char(':'),
digit1,
)),
|(name, _, value)| (name.to_string(), value.parse().unwrap())
)(input)
}
let result = parse_pair("count:42");
let (_, (name, value)) = result.unwrap();
assert_eq!(name, "count");
assert_eq!(value, 42);
// Key points:
// 1. Parsers run in order, consuming input sequentially
// 2. Results collected into tuple matching parser order
// 3. Each parser can return different type
// 4. Fails fast - stops at first failure
// 5. Works with any parser returning IResult
// 6. Combine with map() to create structs
// 7. Use opt() for optional parsers in tuple
// 8. Nest tuples for more than 21 parsers
}
// Key insight:
// nom::sequence::tuple is the fundamental combinator for parsing
// multiple sequential patterns. It takes a tuple of parsers and
// returns a tuple of their results, applying each parser in order
// with remaining input flowing to the next. The result types are
// preserved exactly - if parser 1 returns &str and parser 2 returns
// u32, the tuple returns (&str, u32). This makes tuple ideal for
// structured parsing: protocol headers, record formats, any data
// with multiple consecutive fields. For clean code, combine tuple
// with map() to transform results into domain structs. tuple
// fails fast on any parser failure, making error handling simple.
// The combinator has minimal overhead - just sequential calls and
// tuple construction. For more complex parsing with alternatives
// or backtracking, combine tuple with other combinators like alt().Key insight: nom::sequence::tuple combines multiple parsers by applying them sequentially to input, threading the remaining input through each parser and collecting all results into an output tuple. The key behavior is that each parser consumes its portion of input, and the remaining input after all parsers is returned along with a tuple of each parser's result. The result tuple preserves each parser's output type exactly, enabling type-safe structured parsing. tuple fails fastβreturning an error from the first failing parser. For practical use, combine tuple with map to transform the result tuple directly into domain structs, or use opt for optional fields. The combinator supports 1-21 parsers; for more, nest tuples. This makes tuple the foundational building block for parsing structured formats in nom.
