How does nom::combinator::map transform parser results and when would you use it over nom::combinator::map_res?
nom::combinator::map applies a transformation function to the output of a parser when the parser succeeds, allowing you to convert the parsed value into a different type without affecting the parsing logic. nom::combinator::map_res does the same thing but the transformation function returns a Result, enabling the transformation itself to fail and propagate an error. Use map when your transformation is infallible (e.g., converting between types, wrapping in structs), and use map_res when the transformation might fail (e.g., validation, parsing strings into numbers, checking constraints).
Basic map Usage
use nom::{
IResult,
bytes::complete::tag,
combinator::map,
};
// Parse a string tag and convert to a number
fn parse_number(input: &str) -> IResult<&str, u32> {
map(tag("42"), |_s: &str| 42u32)(input)
}
fn main() {
let result = parse_number("42abc");
println!("{:?}", result);
// Ok(("abc", 42))
}map applies a function to transform the parser's output.
Transforming String Output
use nom::{
IResult,
bytes::complete::take_while,
combinator::map,
character::is_alphabetic,
};
// Parse a name and convert to owned String
fn parse_name(input: &str) -> IResult<&str, String> {
map(
take_while(|c: char| c.is_alphabetic()),
|s: &str| s.to_string(),
)(input)
}
fn main() {
let result = parse_name("hello world");
println!("{:?}", result);
// Ok((" world", "hello"))
}map converts the parsed string slice into an owned String.
Wrapping in Structs
use nom::{
IResult,
bytes::complete::tag,
combinator::map,
};
#[derive(Debug)]
struct Keyword(String);
// Parse a keyword and wrap in a struct
fn parse_keyword(input: &str) -> IResult<&str, Keyword> {
map(tag("fn"), |s: &str| Keyword(s.to_string()))(input)
}
fn main() {
let result = parse_keyword("fn foo()");
println!("{:?}", result);
// Ok((" foo()", Keyword("fn")))
}Use map to wrap parsed values into domain-specific types.
Multiple Fields with map
use nom::{
IResult,
bytes::complete::tag,
combinator::map,
sequence::tuple,
character::complete::digit1,
};
#[derive(Debug)]
struct Point {
x: u32,
y: u32,
}
// Parse coordinates and create a Point
fn parse_point(input: &str) -> IResult<&str, Point> {
map(
tuple((digit1, tag(","), digit1)),
|(x, _, y): (&str, &str, &str)| Point {
x: x.parse().unwrap(),
y: y.parse().unwrap(),
},
)(input)
}
fn main() {
let result = parse_point("10,20extra");
println!("{:?}", result);
// Ok(("extra", Point { x: 10, y: 20 }))
}map combines multiple parsed values into a single struct.
map_res for Fallible Transformations
use nom::{
IResult,
bytes::complete::digit1,
combinator::map_res,
error::Error,
};
// Parse digits and convert to number (may fail)
fn parse_u32(input: &str) -> IResult<&str, u32, Error<&str>> {
map_res(digit1, |s: &str| s.parse::<u32>())(input)
}
fn main() {
let result = parse_u32("123abc");
println!("{:?}", result);
// Ok(("abc", 123))
// Overflow fails
let result = parse_u32("999999999999999999999");
println!("{:?}", result);
// Err(Error(...)) - number too large
}map_res allows the transformation to return a Result.
Validation with map_res
use nom::{
IResult,
bytes::complete::digit1,
combinator::map_res,
error::{Error, ErrorKind},
};
// Parse a positive number and validate it's within range
fn parse_percentage(input: &str) -> IResult<&str, u8, Error<&str>> {
map_res(
digit1,
|s: &str| -> Result<u8, &'static str> {
let n: u32 = s.parse().map_err(|_| "parse failed")?;
if n > 100 {
Err("percentage must be <= 100")
} else {
Ok(n as u8)
}
},
)(input)
}
fn main() {
let result = parse_percentage("50%");
println!("{:?}", result);
// Ok(("%", 50))
let result = parse_percentage("150%");
println!("{:?}", result);
// Err(Error { ... })
}Use map_res to validate values during parsing.
Custom Error Types
use nom::{
IResult,
bytes::complete::digit1,
combinator::map_res,
error::{Error, ParseError},
};
#[derive(Debug)]
enum ParseError {
InvalidNumber,
OutOfRange,
}
// Parse with custom error type
fn parse_port(input: &str) -> IResult<&str, u16, Error<&str>> {
map_res(
digit1,
|s: &str| -> Result<u16, nom::Err<Error<&str>>> {
let n: u32 = s.parse().map_err(|_| {
nom::Err::Error(Error::new(s, nom::error::ErrorKind::Digit))
})?;
if n > 65535 {
return Err(nom::Err::Error(Error::new(s, nom::error::ErrorKind::Verify)));
}
Ok(n as u16)
},
)(input)
}
fn main() {
let result = parse_port("8080");
println!("{:?}", result);
// Ok(("", 8080))
}map_res can return custom error types for better error messages.
Infallible vs Fallible Transformations
use nom::{
IResult,
bytes::complete::{tag, take},
combinator::{map, map_res},
character::complete::digit1,
};
// Infallible transformation - always succeeds
fn parse_uppercase(input: &str) -> IResult<&str, String> {
map(take(3usize), |s: &str| s.to_uppercase())(input)
}
// Fallible transformation - may fail
fn parse_age(input: &str) -> IResult<&str, u8> {
map_res(digit1, |s: &str| {
let n: u32 = s.parse()?;
if n > 150 {
Err("age too large")
} else {
Ok(n as u8)
}
})(input)
}
fn main() {
let result = parse_uppercase("abc");
println!("{:?}", result); // Ok(("", "ABC"))
let result = parse_age("25");
println!("{:?}", result); // Ok(("", 25))
let result = parse_age("200");
println!("{:?}", result); // Err(...)
}Use map for infallible, map_res for fallible transformations.
String to Number Parsing
use nom::{
IResult,
bytes::complete::digit1,
combinator::map_res,
};
fn parse_int(input: &str) -> IResult<&str, i32> {
map_res(digit1, |s: &str| s.parse::<i32>())(input)
}
fn parse_hex(input: &str) -> IResult<&str, u32> {
map_res(
nom::bytes::complete::take_while(|c: char| c.is_ascii_hexdigit()),
|s: &str| u32::from_str_radix(s, 16),
)(input)
}
fn main() {
let result = parse_int("123abc");
println!("{:?}", result); // Ok(("abc", 123))
let result = parse_hex("ff00abc");
println!("{:?}", result); // Ok(("abc", 65280))
}map_res is commonly used for string-to-number conversion.
Combining Multiple Parsers
use nom::{
IResult,
bytes::complete::tag,
combinator::map,
sequence::tuple,
};
#[derive(Debug)]
struct KeyValue {
key: String,
value: String,
}
fn parse_key_value(input: &str) -> IResult<&str, KeyValue> {
map(
tuple((
nom::bytes::complete::take_while(|c: char| c.is_alphabetic()),
tag("="),
nom::bytes::complete::take_while(|c: char| c.is_alphanumeric()),
)),
|(key, _, value): (&str, &str, &str)| KeyValue {
key: key.to_string(),
value: value.to_string(),
},
)(input)
}
fn main() {
let result = parse_key_value("name=alice rest");
println!("{:?}", result);
// Ok((" rest", KeyValue { key: "name", value: "alice" }))
}map combines results from tuple into a structured type.
Parsing with Context
use nom::{
IResult,
bytes::complete::tag,
combinator::{map, map_res},
sequence::preceded,
};
enum Value {
Number(u32),
String(String),
}
fn parse_value(input: &str) -> IResult<&str, Value> {
// Try number first
let (input, _) = tag("value:")(input)?;
map_res(
nom::bytes::complete::take_while(|c: char| c.is_digit(10)),
|s: &str| -> Result<Value, &'static str> {
s.parse::<u32>()
.map(Value::Number)
.or_else(|_| {
// If parsing fails, treat as string
// This approach has issues - see better version below
Err("not a number")
})
},
)(input)
}
// Better approach: try alternatives
fn parse_value_better(input: &str) -> IResult<&str, Value> {
let (input, _) = tag("value:")(input)?;
nom::branch::alt((
map_res(
nom::character::complete::digit1,
|s: &str| s.parse::<u32>().map(Value::Number),
),
map(
nom::bytes::complete::take_while(|c: char| c.is_alphabetic()),
|s: &str| Value::String(s.to_string()),
),
))(input)
}Use alt with map and map_res for alternatives.
Error Handling in map_res
use nom::{
IResult,
bytes::complete::digit1,
combinator::map_res,
error::{Error, ErrorKind, ParseError},
};
#[derive(Debug)]
enum ValidationError {
ParseError,
TooLarge,
}
fn parse_byte(input: &str) -> IResult<&str, u8, Error<&str>> {
map_res(digit1, |s: &str| {
let n: u32 = s.parse().map_err(|_| ValidationError::ParseError)?;
if n > 255 {
Err(ValidationError::TooLarge)
} else {
Ok(n as u8)
}
})(input)
.map_err(|e: nom::Err<Error<&str>>| {
// Transform error type if needed
e
})
}
fn main() {
let result = parse_byte("300");
println!("{:?}", result); // Err(...)
}map_res propagates transformation errors through the parser.
map_parser for Nested Parsing
use nom::{
IResult,
bytes::complete::take,
combinator::map_parser,
};
// Parse bytes, then parse the result
fn parse_nested(input: &str) -> IResult<&str, u32> {
// Take 3 characters, then parse them as a number
map_parser(take(3usize), |s: &str| {
s.parse::<u32>().map(|n| Ok(("", n)))
.unwrap_or(Err(nom::error::Error::new(s, nom::error::ErrorKind::Digit)))
})(input)
}
// Using map with map_parser
fn parse_and_transform(input: &str) -> IResult<&str, String> {
map(
map_parser(take(3usize), parse_three_digits),
|n| format!("value: {}", n),
)(input)
}
fn parse_three_digits(input: &str) -> IResult<&str, u32> {
nom::character::complete::digit1(input)
.and_then(|(remaining, digits)| {
digits.parse::<u32>()
.map(|n| Ok((remaining, n)))
.unwrap_or(Err(nom::error::Error::new(input, nom::error::ErrorKind::Digit)))
})
}
fn main() {
let result = parse_nested("123rest");
println!("{:?}", result);
}map_parser applies a parser to the output of another parser.
Performance Considerations
use nom::{
IResult,
bytes::complete::{tag, take},
combinator::{map, map_res},
};
// map is slightly faster for infallible transformations
fn fast_parse(input: &str) -> IResult<&str, String> {
map(take(5usize), |s: &str| s.to_string())(input)
}
// map_res has overhead for Result handling
fn slower_parse(input: &str) -> IResult<&str, u32> {
map_res(take(5usize), |s: &str| s.parse())(input)
}
// Prefer map when transformation cannot fail
fn preferred_parse(input: &str) -> IResult<&str, String> {
map(tag("constant"), |_| String::from("CONSTANT"))(input)
}Use map for infallible transformations to avoid Result overhead.
Complete Parsing Example
use nom::{
IResult,
bytes::complete::{tag, take_while},
combinator::{map, map_res},
sequence::tuple,
character::complete::{digit1, char},
};
#[derive(Debug)]
struct Person {
name: String,
age: u8,
}
fn parse_name(input: &str) -> IResult<&str, String> {
map(
take_while(|c: char| c.is_alphabetic()),
|s: &str| s.to_string(),
)(input)
}
fn parse_age(input: &str) -> IResult<&str, u8> {
map_res(digit1, |s: &str| {
let n: u32 = s.parse()?;
if n > 150 {
Err("age too large")
} else {
Ok(n as u8)
}
})(input)
}
fn parse_person(input: &str) -> IResult<&str, Person> {
map(
tuple((
parse_name,
tag(","),
parse_age,
)),
|(name, _, age)| Person { name, age },
)(input)
}
fn main() {
let result = parse_person("Alice,30 rest");
println!("{:?}", result);
// Ok((" rest", Person { name: "Alice", age: 30 }))
let result = parse_person("Bob,200 rest");
println!("{:?}", result);
// Err(...) - age too large
}A complete parser combining map and map_res.
Comparison Table
| Aspect | map |
map_res |
|---|---|---|
| Transformation | Infallible (always succeeds) | Fallible (may return Err) |
| Function signature | Fn(T) -> U |
Fn(T) -> Result<U, E> |
| Error handling | Cannot fail | Propagates errors |
| Use case | Type conversion, wrapping | Validation, parsing strings |
| Performance | Slightly faster | Has Result overhead |
Synthesis
Use map when:
- Converting between types (e.g.,
&strtoString) - Wrapping in structs or enums
- Infallible transformations
- Simple arithmetic operations
- Creating domain types from parsed data
Use map_res when:
- Parsing strings into numbers (may overflow)
- Validating constraints (e.g., range checks)
- Any fallible transformation
- Need custom error messages
- Transformation requires validation
Key differences:
map: Transformation function returnsUdirectlymap_res: Transformation function returnsResult<U, E>mapis infallible,map_rescan propagate errorsmaphas slightly better performance
Best practices:
- Use
mapfor all infallible transformations - Use
map_reswhen transformation can fail - Combine with
altfor multiple parsing strategies - Use custom error types for better error messages
Key insight: map transforms parser output while map_res transforms and validates. Both compose cleanly with other nom combinators, but map_res integrates error handling into the transformation. Use map for type conversions and map_res when the transformation itself can fail.
