Loading pageâŚ
Rust walkthroughs
Loading pageâŚ
nom::combinator::map and map_res for parser result transformation?map transforms successful parser output with a fallible function that returns T, while map_res transforms parser output with a fallible function that returns Result<T, E>, converting failures into parse errors. Both combinators apply a function to the output of a successful parse, but they differ in how they handle transformation failures. map assumes the transformation always succeeds, wrapping the result directly. map_res allows the transformation to fail, converting an Err into a parse error that backtracks. This distinction matters when parsing structured data that requires validation or conversionâlike parsing integers within a range, or parsing strings that must match a specific format.
use nom::{combinator::map, bytes::complete::tag, IResult};
fn parse_greeting(input: &str) -> IResult<&str, &str> {
tag("hello")(input)
}
fn main() {
// map transforms successful output
let parser = map(parse_greeting, |s| s.to_uppercase());
let result = parser("hello world");
println!("{:?}", result);
// Ok((" world", "HELLO"))
// Parse failure propagates unchanged
let result = parser("goodbye");
println!("{:?}", result);
// Err(Err::Error(Error::new("goodbye", ErrorKind::Tag)))
}map applies a pure transformation function to successful parse results.
use nom::{combinator::map_res, bytes::complete::tag, IResult};
fn parse_number(input: &str) -> IResult<&str, i32> {
// map_res allows fallible conversion
map_res(
nom::character::complete::digit1,
|s: &str| s.parse::<i32>()
)(input)
}
fn main() {
// Successful parse and conversion
let result = parse_number("123abc");
println!("{:?}", result);
// Ok(("abc", 123))
// Parse succeeds but conversion fails
let result = parse_number("99999999999999999999abc");
println!("{:?}", result);
// Err(Err::Error(...)) - overflow during parse
}map_res handles fallible transformations, converting Err to parse errors.
use nom::{combinator::{map, map_res}, bytes::complete::take_while, IResult};
// map: transformation cannot fail
fn parse_with_map(input: &str) -> IResult<&str, String> {
map(
take_while(|c: char| c.is_alphabetic()),
|s: &str| s.to_string()
)(input)
}
// map_res: transformation can fail
fn parse_with_map_res(input: &str) -> IResult<&str, i32> {
map_res(
take_while(|c: char| c.is_numeric()),
|s: &str| s.parse::<i32>()
)(input)
}
fn main() {
// map always succeeds if parser succeeds
let result = parse_with_map("hello123");
println!("{:?}", result);
// Ok(("123", "hello"))
// map_res can fail even if parser succeeds
let result = parse_with_map_res("123abc");
println!("{:?}", result);
// Ok(("abc", 123))
// Parser succeeds, but conversion fails
let result = parse_with_map_res("99999999999999999999abc");
println!("{:?}", result);
// Err(Error) - parse succeeded but .parse() failed
}map assumes transformation succeeds; map_res handles transformation failures as parse errors.
use nom::{combinator::map_res, character::complete::digit1, IResult};
// Parse integer in a specific range
fn parse_port(input: &str) -> IResult<&str, u16> {
map_res(
digit1,
|s: &str| {
let num: u32 = s.parse()?;
if num > 65535 {
Err("Port out of range")
} else {
Ok(num as u16)
}
}
)(input)
}
fn main() {
// Valid port
let result = parse_port("8080");
println!("{:?}", result);
// Ok(("", 8080))
// Port too large
let result = parse_port("70000");
println!("{:?}", result);
// Err(Error) - "70000" parsed but validation failed
// Not a number
let result = parse_port("abc");
println!("{:?}", result);
// Err(Error) - digit1 failed to parse
}map_res enables validation after successful parsing, converting validation failures to parse errors.
use nom::{combinator::map_res, character::complete::digit1, IResult, error::{Error, ErrorKind}};
#[derive(Debug)]
enum ParseError {
Nom(ErrorKind),
InvalidValue(String),
}
fn parse_positive(input: &str) -> IResult<&str, i32, ParseError> {
map_res(
digit1,
|s: &str| {
let num: i32 = s.parse().map_err(|_| ParseError::InvalidValue(s.to_string()))?;
if num > 0 {
Ok(num)
} else {
Err(ParseError::InvalidValue("must be positive".to_string()))
}
}
)(input)
}
fn main() {
let result = parse_positive("42");
println!("{:?}", result);
// Ok(("", 42))
let result = parse_positive("0");
println!("{:?}", result);
// Err(Error(ParseError::InvalidValue("must be positive")))
let result = parse_positive("-5");
println!("{:?}", result);
// Err(Error(ErrorKind::Digit)) - digit1 failed on '-'
}map_res can produce custom error types for rich error reporting.
use nom::{combinator::{map, map_res}, bytes::complete::take, IResult};
// map can be chained for pure transformations
fn parse_and_transform(input: &[u8]) -> IResult<&[u8], String> {
map(
map(
take(4u8),
|bytes: &[u8]| bytes.len()
),
|len| format!("{} bytes", len)
)(input)
}
// map_res for transformations that might fail
fn parse_and_validate(input: &str) -> IResult<&str, u8> {
map_res(
map_res(
take(2u8),
|s: &str| s.parse::<u8>()
),
|n| {
if n < 100 {
Ok(n)
} else {
Err("Value too large")
}
}
)(input)
}
fn main() {
let result = parse_and_transform(&[1, 2, 3, 4, 5]);
println!("{:?}", result);
// Ok(([5], "4 bytes"))
let result = parse_and_validate("42");
println!("{:?}", result);
// Ok(("", 42))
let result = parse_and_validate("99");
println!("{:?}", result);
// Ok(("", 99))
let result = parse_and_validate("ab");
println!("{:?}", result);
// Err - "ab" can't parse as u8
}Both combinators chain naturally; map_res propagates failures at each step.
use nom::{combinator::map_res, bytes::complete::take_while, character::complete::one_of, IResult};
fn parse_hex(input: &str) -> IResult<&str, u32> {
map_res(
take_while(|c: char| c.is_ascii_hexdigit()),
|s: &str| u32::from_str_radix(s, 16)
)(input)
}
fn main() {
let result = parse_hex("FF00");
println!("{:?}", result);
// Ok(("", 65280))
let result = parse_hex("deadBEEF");
println!("{:?}", result);
// Ok(("", 3735928559))
let result = parse_hex("xyz");
println!("{:?}", result);
// Err - no hex digits found
}map_res naturally handles conversions that can fail, like parsing different number bases.
use nom::{combinator::map_res, bytes::complete::take_while1, IResult};
fn parse_username(input: &str) -> IResult<&str, String> {
map_res(
take_while1(|c: char| c.is_alphanumeric() || c == '_'),
|s: &str| {
if s.len() >= 3 && s.len() <= 20 {
Ok(s.to_string())
} else {
Err("Username must be 3-20 characters")
}
}
)(input)
}
fn parse_email(input: &str) -> IResult<&str, String> {
map_res(
take_while1(|c: char| c != ' ' && c != ','),
|s: &str| {
if s.contains('@') && s.contains('.') {
Ok(s.to_string())
} else {
Err("Invalid email format")
}
}
)(input)
}
fn main() {
let result = parse_username("john_doe");
println!("{:?}", result);
// Ok(("", "john_doe"))
let result = parse_username("ab");
println!("{:?}", result);
// Err - "ab" is too short
let result = parse_email("user@example.com");
println!("{:?}", result);
// Ok(("", "user@example.com"))
let result = parse_email("invalid");
println!("{:?}", result);
// Err - no @ symbol
}Combine parsing with validation using map_res to reject invalid data.
use nom::{combinator::map, character::complete::{digit1, space0}, sequence::tuple, IResult};
fn parse_pair(input: &str) -> IResult<&str, (i32, i32)> {
map(
tuple((
digit1,
space0,
digit1
),
|(a, _, b): (&str, &str, &str)| {
(a.parse().unwrap(), b.parse().unwrap())
}
)(input)
}
fn parse_point(input: &str) -> IResult<&str, Point> {
map(
tuple((
digit1,
nom::character::complete::char(','),
digit1
),
|(x, _, y): (&str, char, &str)| Point {
x: x.parse().unwrap(),
y: y.parse().unwrap(),
}
)(input)
}
struct Point {
x: i32,
y: i32,
}
fn main() {
let result = parse_pair("123 456");
println!("{:?}", result);
// Ok(("", (123, 456)))
let result = parse_point("10,20");
println!("{:?}", result);
// Ok(("", Point { x: 10, y: 20 }))
}map is ideal when you're certain parsing succeeded and transformation won't fail.
use nom::{combinator::{map, map_res}, bytes::complete::take_while1, IResult};
// Use map when:
// 1. Transformation is infallible
// 2. You're just reshaping data
// 3. You're constructing from known-good values
fn parse_uppercase(input: &str) -> IResult<&str, String> {
map(
take_while1(|c: char| c.is_alphabetic()),
|s: &str| s.to_uppercase()
)(input)
}
fn parse_count(input: &str) -> IResult<&str, usize> {
map(
take_while1(|c: char| c.is_alphabetic()),
|s: &str| s.len()
)(input)
}
// Use map_res when:
// 1. Transformation can fail
// 2. You need to validate parsed data
// 3. You're converting to a type that might reject values
fn parse_integer(input: &str) -> IResult<&str, i32> {
map_res(
take_while1(|c: char| c.is_numeric()),
|s: &str| s.parse::<i32>()
)(input)
}
fn parse_range(input: &str) -> IResult<&str, u8> {
map_res(
take_while1(|c: char| c.is_numeric()),
|s: &str| {
let n: u8 = s.parse()?;
if n >= 1 && n <= 100 {
Ok(n)
} else {
Err("Value must be 1-100")
}
}
)(input)
}
fn main() {
// map examples - always succeeds if parser succeeds
println!("{:?}", parse_uppercase("hello")); // Ok(("", "HELLO"))
println!("{:?}", parse_count("hello")); // Ok(("", 5))
// map_res examples - can fail after parser succeeds
println!("{:?}", parse_integer("123")); // Ok(("", 123))
println!("{:?}", parse_range("50")); // Ok(("", 50))
println!("{:?}", parse_range("150")); // Err - out of range
}Choose map for pure transformations; map_res for fallible conversions or validation.
use nom::{combinator::{map, map_res}, bytes::complete::tag, IResult};
fn with_map(input: &str) -> IResult<&str, i32> {
// map: panics or causes issues if transformation fails
// unwrap() in map is dangerous!
map(
tag("value"),
|s: &str| s.parse::<i32>().unwrap() // PANICS on "value"!
)(input)
}
fn with_map_res(input: &str) -> IResult<&str, i32> {
// map_res: gracefully handles conversion failures
map_res(
tag("value"),
|s: &str| s.parse::<i32>() // Returns Err, converted to parse error
)(input)
}
fn main() {
// map version would panic:
// let result = with_map("value"); // PANIC!
// map_res version handles failure:
let result = with_map_res("value");
println!("{:?}", result);
// Err(Error) - "value" isn't a number, but no panic
// Both work for valid input
let result = with_map_res("42");
println!("{:?}", result); // Wait, this won't work with tag("value")
}Never use unwrap or expect inside map if the operation can failâuse map_res instead.
use nom::{combinator::{map, map_res}, bytes::complete::take_while1, character::complete::char, sequence::separated_pair, IResult};
#[derive(Debug)]
struct Config {
timeout_ms: u64,
max_connections: usize,
}
fn parse_u64(input: &str) -> IResult<&str, u64> {
map_res(
take_while1(|c: char| c.is_numeric()),
|s: &str| s.parse::<u64>()
)(input)
}
fn parse_config(input: &str) -> IResult<&str, Config> {
map(
separated_pair(
parse_u64,
char(','),
parse_u64
),
|(timeout, max_conn)| Config {
timeout_ms: timeout,
max_connections: max_conn as usize,
}
)(input)
}
fn main() {
let result = parse_config("5000,100");
println!("{:?}", result);
// Ok(("", Config { timeout_ms: 5000, max_connections: 100 }))
let result = parse_config("invalid,100");
println!("{:?}", result);
// Err - first number failed to parse
}Parse raw values with map_res, then construct types with map.
use nom::{combinator::{map, map_res}, bytes::complete::take_while1, IResult};
// map has slightly better performance:
// - No error handling overhead
// - Compiler can optimize better
// - Fewer branches in generated code
fn fast_parse(input: &str) -> IResult<&str, usize> {
map(
take_while1(|c: char| c.is_alphabetic()),
|s: &str| s.len()
)(input)
}
// map_res has small overhead:
// - Must check Result after transformation
// - Creates error values on failure
// - May need to convert error types
fn validated_parse(input: &str) -> IResult<&str, i32> {
map_res(
take_while1(|c: char| c.is_numeric()),
|s: &str| s.parse::<i32>()
)(input)
}
// Recommendation:
// - Use map when transformation can't fail (best performance)
// - Use map_res when transformation might fail (correctness)
// - Don't prematurely optimize - correctness firstmap has minimal overhead; map_res adds error handling cost but is necessary for fallible transformations.
Core difference:
// map: Transformation returns T
fn map_example(input: &str) -> IResult<&str, String> {
map(parser, |output| transform_to_string(output))(input)
}
// map_res: Transformation returns Result<T, E>
fn map_res_example(input: &str) -> IResult<&str, i32> {
map_res(parser, |output| try_convert(output))(input)
}Behavior comparison:
| Aspect | map | map_res |
|--------|-------|-----------|
| Transform return type | T | Result<T, E> |
| Parser failure | Propagates error | Propagates error |
| Transform failure | N/A (can't fail) | Becomes parse error |
| Use case | Pure transformations | Fallible conversions |
| Performance | Slightly faster | Small overhead |
When to use map:
&str length to usizeWhen to use map_res:
str::parse::<T>() for any TKey insight: The distinction between map and map_res mirrors Rust's separation of fallible and infallible operations. Use map when your transformation is pure and cannot failâthe parser's success guarantees the transformation's success. Use map_res when parsing is only the first step and validation or conversion followsâwhere successfully parsed input might still be semantically invalid. This pattern keeps error handling explicit and prevents panics from unwrap calls hidden inside map transformations.