Loading pageā¦
Rust walkthroughs
Loading pageā¦
nom::sequence::tuple and preceded for parser composition ordering?nom::sequence::tuple and preceded serve fundamentally different purposes in parser composition: tuple applies multiple parsers in sequence and returns all results as a tuple, preserving every parsed value for later use, while preceded applies two parsers in sequence but discards the first result, returning only the second. The key distinction is what happens to parsed values: tuple collects all outputs, making it useful when every component of a sequence matters to the final result. preceded intentionally discards output, making it ideal for parsing syntax that carries semantic meaning (like keywords, delimiters, or whitespace) but whose literal value isn't needed in the abstract syntax tree. This difference in output handling leads to different use cases: tuple for "parse A then B then C, keep all three" versus preceded for "parse A then B, throw away A, keep B."
use nom::{IResult, bytes::complete::tag, sequence::{tuple, preceded}, character::complete::digit1};
// In nom, parsers have the signature:
// fn parser(input: &str) -> IResult<&str, Output>
// Where IResult<&str, Output> = Result<(&str, Output), nom::Err<Error>>
// The remaining input and parsed value are both returned on success
fn main() {
let input = "hello world";
// A simple parser: tag matches a literal string
let result: IResult<&str, &str> = tag("hello")(input);
println!("Result: {:?}", result);
// Ok((" world", "hello"))
// Remaining input: " world"
// Parsed value: "hello"
}All nom parsers return both remaining input and parsed value.
use nom::{IResult, bytes::complete::tag, sequence::tuple, character::complete::{digit1, alpha1}};
fn main() {
let input = "abc123xyz";
// tuple applies multiple parsers in sequence
// Returns a tuple of all results
let parser = tuple((
alpha1, // Parse letters
digit1, // Parse digits
alpha1, // Parse letters
));
let result: IResult<&str, (&str, &str, &str)> = parser(input);
println!("Result: {:?}", result);
// Ok(("", ("abc", "123", "xyz")))
// All three results are preserved
// tuple preserves order: first parser result is first in tuple
// Every parsed value is available in the output tuple
}tuple returns a tuple containing all parser results in order.
use nom::{IResult, bytes::complete::tag, sequence::preceded, character::complete::digit1};
fn main() {
let input = "prefix123";
// preceded applies two parsers in sequence
// Returns ONLY the second result
let parser = preceded(
tag("prefix"), // First parser (discarded)
digit1, // Second parser (kept)
);
let result: IResult<&str, &str> = parser(input);
println!("Result: {:?}", result);
// Ok(("", "123"))
// "prefix" was parsed but discarded
// Only "123" appears in the output
// preceded is for "skip this, then parse that"
// Useful when the first parser is syntax that doesn't carry needed data
}preceded discards the first parser's result, returning only the second.
use nom::{IResult, bytes::complete::tag, sequence::{tuple, preceded}, character::complete::{alpha1, digit1}};
fn main() {
let input = "abc123";
// Using tuple: get all results
let tuple_parser = tuple((alpha1, digit1));
let tuple_result = tuple_parser(input);
match tuple_result {
Ok((remaining, (letters, digits))) => {
println!("Tuple result: letters='{}', digits='{}', remaining='{}'",
letters, digits, remaining);
}
Err(e) => println!("Error: {:?}", e),
}
// Using preceded: get only second result
let preceded_parser = preceded(alpha1, digit1);
let preceded_result = preceded_parser(input);
match preceded_result {
Ok((remaining, digits)) => {
println!("Preceded result: digits='{}', remaining='{}'", digits, remaining);
// Note: letters (alpha1 result) is not available!
}
Err(e) => println!("Error: {:?}", e),
}
// The key difference:
// tuple((A, B)) -> (A_output, B_output)
// preceded(A, B) -> B_output only
}tuple preserves all outputs; preceded discards the first.
use nom::{IResult, bytes::complete::tag, sequence::{tuple, preceded, succeeded, delimited}, character::complete::digit1};
fn main() {
// The nom sequence module has several related combinators:
// preceded(A, B): Parse A, then B, return B
// "Skip A, keep B"
// succeeded(A, B): Parse A, then B, return A
// "Keep A, skip B"
// delimited(A, B, C): Parse A, B, C in order, return B
// "Skip surrounding, keep middle"
let input = "(123)";
// delimited is like preceded for both sides
let parser = delimited(
tag("("), // Opening delimiter (discarded)
digit1, // Content (kept)
tag(")") // Closing delimiter (discarded)
);
let result: IResult<&str, &str> = parser(input);
println!("delimited result: {:?}", result);
// Ok(("", "123"))
// Only the middle result is kept
}The sequence module provides combinators for different output retention patterns.
use nom::{IResult, bytes::complete::tag, sequence::tuple, character::complete::{alpha1, digit1, space1}};
// Use tuple when you need ALL parsed values in your result
#[derive(Debug)]
struct KeyValue {
key: String,
value: String,
}
fn parse_key_value(input: &str) -> IResult<&str, KeyValue> {
let (remaining, (key, _, value)) = tuple((
alpha1, // Key (needed)
tag("="), // Separator (not needed in output, but...)
alpha1, // Value (needed)
))(input)?;
// We need both key and value, so tuple is appropriate
Ok((remaining, KeyValue {
key: key.to_string(),
value: value.to_string(),
}))
}
fn main() {
let input = "name=value";
let result = parse_key_value(input);
println!("{:?}", result);
// tuple is appropriate when:
// 1. Multiple parsed values contribute to your result
// 2. Order matters for interpreting results
// 3. You're building up components of a data structure
}Use tuple when multiple parsed values are needed in the final result.
use nom::{IResult, bytes::complete::tag, sequence::preceded, character::complete::{alpha1, digit1, multispace0}};
// Use preceded when the first parser is syntax but not data
#[derive(Debug)]
struct Number {
value: i32,
}
fn parse_number_with_prefix(input: &str) -> IResult<&str, Number> {
let (remaining, digits) = preceded(
tag("number:"), // Keyword (syntax, not data)
digit1, // Actual value (data)
)(input)?;
// "number:" is syntax - we need to parse it but don't care about its value
// The actual number is what we want
Ok((remaining, Number {
value: digits.parse().unwrap(),
}))
}
fn parse_with_whitespace(input: &str) -> IResult<&str, &str> {
// preceded is perfect for skipping whitespace
preceded(
multispace0, // Skip leading whitespace (discard)
alpha1, // Parse the actual content (keep)
)(input)
}
fn main() {
let input = "number:42";
let result = parse_number_with_prefix(input);
println!("{:?}", result);
let with_ws = " hello";
let ws_result = parse_with_whitespace(with_ws);
println!("Whitespace result: {:?}", ws_result);
// preceded is appropriate when:
// 1. First parser is syntax/keyword/delimiter
// 2. First parser's value doesn't contribute to result
// 3. Only second parser's value matters
}Use preceded when the first parser is syntactic structure without semantic value.
use nom::{IResult, bytes::complete::tag, sequence::{tuple, preceded, delimited}, character::complete::{alpha1, digit1, multispace0}};
#[derive(Debug)]
struct Assignment {
name: String,
value: i32,
}
fn parse_assignment(input: &str) -> IResult<&str, Assignment> {
// Combine tuple and preceded for complex parsing
let (remaining, (name, value)) = tuple((
alpha1, // Variable name (keep)
preceded(
tag("="), // Equals sign (discard)
digit1, // Value (keep)
),
))(input)?;
// Alternative: use preceded inside tuple
let (remaining, (name, value)) = tuple((
alpha1,
preceded(tag("="), digit1),
))(input)?;
// We keep name and value, discard "="
Ok((remaining, Assignment {
name: name.to_string(),
value: value.parse().unwrap(),
}))
}
fn main() {
let input = "x=42";
let result = parse_assignment(input);
println!("{:?}", result);
}Nest preceded inside tuple to discard intermediate syntax while keeping values.
use nom::{IResult, bytes::complete::tag, sequence::{tuple, preceded, delimited},
character::complete::{alpha1, digit1, multispace0}, multi::separated_list0};
#[derive(Debug)]
struct FunctionCall {
name: String,
arguments: Vec<String>,
}
fn parse_function_call(input: &str) -> IResult<&str, FunctionCall> {
// Parse: function_name(arg1, arg2, ...)
let (remaining, (name, args)) = tuple((
alpha1, // Function name (keep)
delimited(
tag("("), // Open paren (discard)
separated_list0(tag(","), alpha1), // Arguments (keep)
tag(")"), // Close paren (discard)
),
))(input)?;
Ok((remaining, FunctionCall {
name: name.to_string(),
arguments: args.iter().map(|s| s.to_string()).collect(),
}))
}
fn main() {
let input = "process(data, value)";
let result = parse_function_call(input);
println!("{:?}", result);
// Here we use:
// - tuple to get both name and arguments
// - delimited to discard parentheses but keep arguments
// The result has semantic data without syntactic noise
}Combine combinators to keep semantic data while discarding syntactic structure.
use nom::{IResult, bytes::complete::tag, sequence::{tuple, preceded}, character::complete::{alpha1, digit1, multispace0}};
#[derive(Debug)]
enum Statement {
Let { name: String, value: i32 },
Print(String),
}
fn parse_let(input: &str) -> IResult<&str, Statement> {
// Parse: let name = value
let (remaining, (name, value)) = preceded(
tuple((tag("let"), multispace0)), // "let " keyword (discard)
tuple((
alpha1, // Variable name (keep)
preceded(
tuple((tag("="), multispace0)), // "= " (discard)
digit1, // Value (keep)
),
)),
)(input)?;
Ok((remaining, Statement::Let {
name: name.to_string(),
value: value.parse().unwrap(),
}))
}
fn parse_print(input: &str) -> IResult<&str, Statement> {
// Parse: print name
let (remaining, name) = preceded(
tuple((tag("print"), multispace0)), // "print " keyword (discard)
alpha1, // Variable name (keep)
)(input)?;
Ok((remaining, Statement::Print(name.to_string())))
}
fn main() {
let let_stmt = "let x = 42";
let print_stmt = "print x";
println!("Let: {:?}", parse_let(let_stmt));
println!("Print: {:?}", parse_print(print_stmt));
}Keywords are parsed but discarded; variable names and values are kept.
use nom::{IResult, bytes::complete::tag, sequence::{tuple, preceded}, character::complete::digit1, error::Error};
fn main() {
// Both combinators fail if any parser fails
let input = "abc123";
// tuple fails if first parser fails
let tuple_result: IResult<&str, (&str, &str)> = tuple((digit1, alpha1))(input);
println!("tuple on mismatched input: {:?}", tuple_result);
// Err: digit1 expected digits, found "abc"
// preceded fails if first parser fails
let preceded_result: IResult<&str, &str> = preceded(digit1, alpha1)(input);
println!("preceded on mismatched input: {:?}", preceded_result);
// Err: digit1 expected digits, found "abc"
// Both fail at the same point, but preceded discards the first result
// on success, while tuple keeps it
let input2 = "123abc";
let tuple_ok: IResult<&str, (&str, &str)> = tuple((digit1, alpha1))(input2);
println!("tuple success: {:?}", tuple_ok);
// Ok(("", ("123", "abc")))
let preceded_ok: IResult<&str, &str> = preceded(digit1, alpha1)(input2);
println!("preceded success: {:?}", preceded_ok);
// Ok(("", "abc"))
}Both combinators have the same failure behavior; they differ only in output.
use nom::{IResult, bytes::complete::tag, sequence::{tuple, preceded}, character::complete::digit1};
fn main() {
let input = "prefix123";
// Both have similar performance characteristics:
// - Apply parsers in sequence
// - Early termination on failure
// - Single pass over input
// The overhead of tuple vs preceded is minimal:
// - tuple constructs a tuple of results
// - preceded only keeps one result
// For most parsers, the difference is negligible
// The choice should be based on semantics, not performance
// Use preceded when you explicitly don't need the first result
// This makes your intent clear and avoids unused variable warnings
let preceded_result: IResult<&str, &str> = preceded(tag("prefix"), digit1)(input);
println!("preceded: {:?}", preceded_result);
// Don't do this (creates unused value):
// let tuple_result = tuple((tag("prefix"), digit1))(input);
// let (_, digits) = tuple_result; // First value ignored anyway
// Do this instead:
// let preceded_result = preceded(tag("prefix"), digit1)(input);
}Choose based on semantics; performance differences are minimal.
// Aspect | tuple | preceded
// --------------------|--------------------------------|--------------------------------
// Number of parsers | 2 or more | Exactly 2
// Output | Tuple of all results | Only second result
// First result | Kept in tuple | Discarded
// Use case | Need all parsed values | First is syntax, not data
// Common pattern | Data extraction | Skip syntax, keep data
// Can be nested | Yes | Yes
// Failure behavior | Fail on any parser failure | Fail on any parser failure
fn main() {
use nom::{IResult, bytes::complete::tag, sequence::{tuple, preceded}, character::complete::digit1};
let input = "abc123";
// tuple: keep all
let t: IResult<&str, (&str, &str)> = tuple((tag("abc"), tag("123")))(input);
println!("tuple: {:?}", t); // Ok(("", ("abc", "123")))
// preceded: keep only second
let p: IResult<&str, &str> = preceded(tag("abc"), tag("123"))(input);
println!("preceded: {:?}", p); // Ok(("", "123"))
}Core distinction:
| Combinator | Parsers | Output | Use When |
|------------|---------|--------|----------|
| tuple | 2+ | All results | Every parsed value matters |
| preceded | 2 | Second only | First parser is syntax (discard) |
| succeeded | 2 | First only | Second parser is syntax (discard) |
| delimited | 3 | Middle only | Surrounding syntax (discard both) |
Decision guide:
| Scenario | Recommended |
|----------|-------------|
| Parse keyword then data | preceded |
| Parse multiple values needed in AST | tuple |
| Parse delimiters around content | delimited |
| Parse content then terminator | succeeded |
| Need some but not all results | Combine preceded/succeeded with tuple |
Key insight: The difference between tuple and preceded is fundamentally about what you're parsing: data or syntax. tuple treats all parsers as equally importantāevery result contributes to your final data structure, like parsing coordinates (x, y) where both values matter. preceded treats the first parser as incidental syntax that must be present but whose value doesn't contribute to the result, like parsing let x = 5 where let and = are necessary syntax but only x and 5 are data. This distinction is about intent and clarity: using preceded when you don't need the first result makes your parser's purpose explicit, avoiding the code smell of tuple results with underscore-prefixed variables like (_, value). The combinators are composable precisely because they have the same failure behavior and input consumption patternāyou can freely nest them to build parsers that cleanly separate syntax from semantics. In a well-designed parser using these combinators appropriately, the parse tree you build contains only semantic data; all syntactic noise has been filtered out through combinators like preceded, succeeded, and delimited.