Loading pageā¦
Rust walkthroughs
Loading pageā¦
nom::sequence::tuple combine multiple parsers into a single tuple result?nom::sequence::tuple takes multiple parsers as arguments and returns a parser that applies each one in sequence, collecting all successful results into a tuple with the same arity as the input parsersāif any parser fails, the entire tuple parser fails and backtracks. The function tuple((parser1, parser2, parser3)) produces a parser that returns ((I, O1, O2, O3), I) on success, where I is the remaining input and O1, O2, O3 are the outputs of each sub-parser. This enables building complex parsers from simpler ones while preserving all parsed values in a structured result.
use nom::{sequence::tuple, character::complete::{digit1, alpha1}, IResult};
fn basic_tuple() {
// Combine two parsers
let mut parser = tuple((alpha1, digit1));
// Parse "abc123"
let result: IResult<&str, (&str, &str)> = parser("abc123");
match result {
Ok((remaining, (letters, numbers))) => {
assert_eq!(remaining, "");
assert_eq!(letters, "abc");
assert_eq!(numbers, "123");
}
Err(e) => panic!("Parse failed: {:?}", e),
}
}tuple returns a tuple of all parser results in order.
use nom::{sequence::tuple, character::complete::{digit1, alpha1, char}, IResult};
fn multiple_parsers() {
// Combine three parsers
let mut parser = tuple((alpha1, char(':'), digit1));
let result: IResult<&str, (&str, char, &str)> = parser("key:42");
match result {
Ok((remaining, (key, colon, value))) => {
assert_eq!(remaining, "");
assert_eq!(key, "key");
assert_eq!(colon, ':');
assert_eq!(value, "42");
}
Err(e) => panic!("Parse failed: {:?}", e),
}
}The output tuple grows with each additional parser.
use nom::{IResult, InputLength, Parser};
// The tuple function signature (simplified):
// pub fn tuple<I, O>(mut tuple: (P1, P2, ...)) -> impl Fn(I) -> IResult<I, (O1, O2, ...)>
// where
// P1: Parser<I, O1>,
// P2: Parser<I, O2>,
// ...
// The input is a tuple of parsers
// The output is a parser that produces a tuple of outputs
fn type_signature() {
// Input: (Parser<I, O1>, Parser<I, O2>, Parser<I, O3>)
// Output: impl Parser<I, (O1, O2, O3)>
// The input and output tuple arities match
// Each parser in the input contributes one element to the output
}The output tuple has the same number of elements as the input parser tuple.
use nom::{sequence::tuple, character::complete::{digit1, alpha1}, error::ErrorKind, IResult};
fn failure_propagation() {
let mut parser = tuple((alpha1, digit1));
// First parser fails
let result = parser("123abc");
assert!(result.is_err());
// The entire tuple fails; digit1 is never called
// Second parser fails
let result = parser("abcdef");
assert!(result.is_err());
// alpha1 succeeds ("abcdef"), then digit1 fails
// Partial consumption behavior:
// On failure, the input is NOT consumed
// The parser backtracks to the original position
}If any parser fails, the entire tuple fails without consuming input.
use nom::{sequence::tuple, character::complete::{alpha1, digit1, multispace0}, IResult};
fn sequential_application() {
let mut parser = tuple((alpha1, multispace0, digit1));
// Each parser is applied to the remaining input from the previous
let result = parser("word 123extra");
match result {
Ok((remaining, (word, spaces, number))) => {
// word is parsed first from full input
assert_eq!(word, "word");
// spaces is parsed from " 123extra"
assert_eq!(spaces, " ");
// number is parsed from "123extra"
assert_eq!(number, "123");
// remaining is what's left
assert_eq!(remaining, "extra");
}
Err(_) => panic!("Should succeed"),
}
}Each parser sees the input remaining after the previous parser.
use nom::{sequence::tuple, character::complete::{char, digit1}, combinator::map, IResult};
fn with_map() {
// Transform tuple output into a struct
#[derive(Debug, PartialEq)]
struct Point {
x: i32,
y: i32,
}
let mut parser = map(
tuple((char('('), digit1, char(','), digit1, char(')'))),
|(_, x, _, y, _): (char, &str, char, &str, char)| Point {
x: x.parse().unwrap(),
y: y.parse().unwrap(),
}
);
let result = parser("(3,4)");
assert_eq!(result, Ok(("", Point { x: 3, y: 4 })));
}Combine tuple with map to transform results into structured types.
use nom::{sequence::tuple, character::complete::{char, digit1}, combinator::map, IResult};
fn ignoring_values() {
// When some parsed values are not needed
let mut parser = map(
tuple((char('('), digit1, char(','), digit1, char(')'))),
|(_, x, _, y, _)| (x, y) // Keep only the digits
);
let result: IResult<&str, (&str, &str)> = parser("(10,20)");
assert_eq!(result, Ok(("", ("10", "20"))));
}
// Alternative: use preceded, delimited, separated_pair
use nom::sequence::{preceded, delimited, separated_pair};
fn alternatives() {
// separated_pair is more idiomatic for this pattern
let mut parser = separated_pair(digit1, char(','), digit1);
let result = parser("10,20");
assert_eq!(result, Ok(("", ("10", "20"))));
// delimited for wrapped content
let mut parser = delimited(char('('), separated_pair(digit1, char(','), digit1), char(')'));
let result = parser("(10,20)");
assert_eq!(result, Ok(("", ("10", "20"))));
}For common patterns, specialized combinators may be more readable than tuple.
use nom::{sequence::tuple, character::complete::{alpha1, digit1, char}, IResult};
fn nested_tuples() {
// Tuple parsers can be nested
let mut parser = tuple((
alpha1,
tuple((char(':'), digit1)),
alpha1
));
let result: IResult<&str, (&str, (char, &str), &str)> = parser("a:123b");
match result {
Ok((remaining, (first, (_colon, number), last))) => {
assert_eq!(first, "a");
assert_eq!(number, "123");
assert_eq!(last, "b");
assert_eq!(remaining, "");
}
Err(_) => panic!("Should succeed"),
}
}Tuples can contain other tuples, creating nested result structures.
use nom::{sequence::tuple, character::complete::alpha1, IResult};
fn zero_one_element() {
// Zero-element tuple (unit type)
let mut empty_parser = tuple(());
let result: IResult<&str, ()> = empty_parser("anything");
assert_eq!(result, Ok(("anything", ())));
// Always succeeds, consumes nothing
// One-element tuple (wraps in single-element tuple)
let mut single_parser = tuple((alpha1,));
let result: IResult<&str, (&str,)> = single_parser("abc");
assert_eq!(result, Ok(("", ("abc",))));
// Note the trailing comma for single-element tuple syntax
}Empty tuples always succeed; single-element tuples wrap the result.
use nom::{
sequence::tuple,
character::complete::{char, digit1, alpha1},
bytes::complete::tag,
IResult,
};
fn different_types() {
// Each parser can return a different type
let mut parser = tuple((
alpha1, // returns &str
char(':'), // returns char
digit1, // returns &str
tag("end"), // returns &str
));
let result: IResult<&str, (&str, char, &str, &str)> = parser("key:123end");
match result {
Ok((remaining, (alpha, colon, digits, tag_result))) => {
assert_eq!(alpha, "key");
assert_eq!(colon, ':');
assert_eq!(digits, "123");
assert_eq!(tag_result, "end");
assert_eq!(remaining, "");
}
Err(_) => panic!("Should succeed"),
}
}Each parser in the tuple contributes its own return type to the output tuple.
use nom::{
sequence::tuple,
character::complete::{char, digit1, multispace0},
combinator::map,
IResult,
};
fn structured_data() {
// Parse a key-value pair with type annotation
// Format: "name : type = value"
#[derive(Debug, PartialEq)]
struct Field {
name: String,
type_name: String,
value: String,
}
let mut parser = map(
tuple((
alpha1, // name
multispace0,
char(':'),
multispace0,
alpha1, // type
multispace0,
char('='),
multispace0,
digit1, // value
)),
|(name, _, _, _, type_name, _, _, _, value)| Field {
name: name.to_string(),
type_name: type_name.to_string(),
value: value.to_string(),
}
);
let result = parser("count : int = 42");
assert_eq!(result, Ok(("", Field {
name: "count".to_string(),
type_name: "int".to_string(),
value: "42".to_string(),
})));
}Complex structured parsing uses tuple to sequence all components.
use nom::{sequence::tuple, character::complete::{alpha1, digit1}, error::{Error, ErrorKind}, IResult};
fn error_handling() {
let mut parser = tuple((alpha1, digit1));
// Error on first parser
let result = parser("123");
match result {
Err(nom::Err::Error(Error { input, code })) => {
// input is the original input
// code indicates what failed
assert_eq!(input, "123");
// alpha1 failed
}
_ => panic!("Should error"),
}
// Error on second parser
let result = parser("abc");
match result {
Err(nom::Err::Error(Error { input, code })) => {
// input is what digit1 saw ("")
// because alpha1 consumed "abc"
// But nom backtracks, so original input is restored
// (depending on error mode)
}
_ => panic!("Should error"),
}
}Errors indicate which parser failed; input position is restored on failure.
use nom::{
sequence::tuple,
character::complete::digit1,
character::streaming::digit1 as streaming_digit1,
IResult,
};
fn complete_vs_streaming() {
// Complete mode: assumes input is complete
let mut complete_parser = tuple((digit1::<&str, nom::error::Error<&str>>, digit1));
// digit1 in complete mode fails if there are no digits
// Streaming mode: may need more input
let mut streaming_parser = tuple((
streaming_digit1::<&str, nom::error::Error<&str>>,
streaming_digit1
));
// digit1 in streaming mode returns Incomplete if insufficient input
// tuple works with both complete and streaming parsers
// All parsers in the tuple should use the same mode
}tuple works with both complete and streaming input modes.
use nom::{sequence::tuple, character::complete::digit1, IResult};
fn performance() {
// tuple does NOT allocate
// It returns a stack-allocated tuple
// Each parser is called in sequence
// No intermediate collections
// The output tuple is constructed after all parsers succeed
// If any fails, no output tuple is created
let mut parser = tuple((digit1, digit1, digit1));
// This is equivalent to manual sequencing:
fn manual_parse(input: &str) -> IResult<&str, (&str, &str, &str)> {
let (input, a) = digit1(input)?;
let (input, b) = digit1(input)?;
let (input, c) = digit1(input)?;
Ok((input, (a, b, c)))
}
// tuple is zero-overhead abstraction over manual sequencing
}tuple is a zero-overhead abstraction; it compiles to sequential parser calls.
use nom::{IResult, Parser};
fn custom_combinator<I, O1, O2, E>(
mut parser1: impl Parser<I, O1, E>,
mut parser2: impl Parser<I, O2, E>,
) -> impl Fn(I) -> IResult<I, (O1, O2), E>
where
I: Clone,
{
move |input: I| {
let (remaining, result1) = parser1.parse(input)?;
let (remaining, result2) = parser2.parse(remaining)?;
Ok((remaining, (result1, result2)))
}
}
// This is essentially what tuple does internally
// The macro system handles arbitrary aritiesThe tuple combinator generalizes this pattern to any number of parsers.
use nom::{
sequence::tuple,
character::complete::{alpha1, digit1, char},
bytes::complete::tag,
combinator::opt,
IResult,
};
fn url_parsing() {
// Simplified URL: scheme://host:port/path
#[derive(Debug)]
struct Url<'a> {
scheme: &'a str,
host: &'a str,
port: Option<u16>,
path: &'a str,
}
fn parse_url(input: &str) -> IResult<&str, Url> {
let (remaining, (scheme, _, host, port, path)) = tuple((
alpha1, // scheme
tag("://"), // separator
alpha1, // host
opt( // optional port
tuple((char(':'), digit1))
),
tag("/"), // path start
))(input)?;
// Remaining is the path
Ok((remaining, Url {
scheme,
host,
port: port.map(|(_, p)| p.parse().unwrap()),
path: remaining,
}))
}
let result = parse_url("http://example:8080/path");
assert!(result.is_ok());
}Complex real-world parsing combines tuple with other combinators.
How tuple combines parsers:
// tuple applies parsers sequentially
// (P1, P2, P3) -> applies P1, then P2, then P3
// Results are collected into tuple: (O1, O2, O3)
// If any parser fails:
// - The entire tuple fails
// - Input is restored to original position
// - No output tuple is created
// On success:
// - Returns remaining input and tuple of results
// - Each result is in the same position as its parserKey characteristics:
// 1. Sequential: parsers run in order
// 2. Result preservation: all outputs are kept
// 3. Atomic failure: any failure fails the whole thing
// 4. Backtracking: input restored on failure
// 5. Zero overhead: compiles to sequential callsWhen to use tuple:
// Use tuple when:
// - You need all parser results
// - Parsers must all succeed
// - Order matters
// - Building structured output
// Use alternatives when:
// - Only some values needed (preceded, delimited)
// - Pairs of values (separated_pair)
// - Optional values (opt)Key insight: nom::sequence::tuple combines multiple parsers by applying them sequentially and collecting all results into a tuple with matching arity. It provides ordered, atomic parsing where all parsers must succeedāany failure causes the entire operation to fail with backtracking. The combinator is zero-overhead and works with any parser types that share the same input. For patterns where only some values are needed, specialized combinators like preceded, delimited, or separated_pair may be more readable, but tuple is the general-purpose building block that underlies them all.