What are the differences between nom::combinator::map and nom::combinator::map_res for transforming parser output?

nom::combinator::map applies an infallible transformation to parser output, converting the successful parse result to a new type without any possibility of failure. nom::combinator::map_res applies a fallible transformation that can fail, returning a Result from the transformation function—if the function returns Err, the parser backtracks and fails as if the parse itself had failed. This distinction matters when parsing requires validation or conversion: map is for pure transformations like converting types, while map_res is for transformations that might fail like parsing strings to integers or validating parsed values. Using map_res allows the transformation failure to integrate with nom's backtracking system, enabling alternative parsers to be tried.

Basic map Usage

use nom::{IResult, bytes::complete::tag, combinator::map};
 
fn main() {
    // map: apply infallible transformation
    let input = "hello world";
    
    let parser = map(tag("hello"), |s: &str| s.to_uppercase());
    
    let result: IResult<&str, String> = parser(input);
    match result {
        Ok((remaining, output)) => {
            println!("Output: {}", output);      // "HELLO"
            println!("Remaining: {}", remaining); // " world"
        }
        Err(e) => println!("Error: {:?}", e),
    }
    
    // map always succeeds if the inner parser succeeds
    // The closure cannot cause parser failure
}

map transforms successful output; the closure cannot fail.

Basic map_res Usage

use nom::{IResult, bytes::complete::tag, combinator::map_res, error::Error};
 
fn main() {
    // map_res: apply fallible transformation
    let input = "hello";
    
    let parser = map_res(tag("hello"), |s: &str| {
        if s.len() > 3 {
            Ok(s.to_uppercase())
        } else {
            Err("string too short")
        }
    });
    
    let result: IResult<&str, String, Error<&str>> = parser(input);
    match result {
        Ok((remaining, output)) => println!("Success: {}", output),
        Err(e) => println!("Error: {:?}", e),
    }
    
    // With failing transformation
    let input2 = "hi";
    let parser2 = map_res(tag("hi"), |_s: &str| -> Result<String, &str> {
        Err("transformation failed")
    });
    
    let result2 = parser2(input2);
    assert!(result2.is_err());
    // Transformation failure becomes parser failure
}

map_res allows transformation to fail, which becomes a parse error.

Parsing Integers

use nom::{IResult, character::complete::digit1, combinator::map_res, error::Error};
 
fn parse_number(input: &str) -> IResult<&str, i32, Error<&str>> {
    // digit1 parses one or more digits
    // map_res converts string to i32, which can fail
    map_res(digit1, |s: &str| s.parse::<i32>())(input)
}
 
fn main() {
    // Valid integer
    let result = parse_number("12345 rest");
    match result {
        Ok((remaining, num)) => {
            println!("Number: {}", num);        // 12345
            println!("Remaining: {:?}", remaining); // " rest"
        }
        Err(e) => println!("Error: {:?}", e),
    }
    
    // Overflow fails
    let overflow_result = parse_number("999999999999999999999 rest");
    assert!(overflow_result.is_err());
    // parse() fails for overflow, map_res propagates as parser error
    
    // Not a digit
    let invalid_result = parse_number("abc");
    assert!(invalid_result.is_err());
    // digit1 fails before map_res is called
}

map_res is essential for parsing strings to types with FromStr.

When to Use map vs map_res

use nom::{IResult, bytes::complete::take, combinator::{map, map_res}};
 
// Use map when transformation cannot fail
fn parse_length(input: &[u8]) -> IResult<&[u8], usize> {
    // take(4) always succeeds if 4 bytes available
    // converting to usize from length calculation cannot fail
    map(take(4u8), |bytes: &[u8]| bytes.len())(input)
}
 
// Use map_res when transformation can fail
fn parse_u32(input: &[u8]) -> IResult<&[u8], u32> {
    // Converting bytes to u32 can fail (wrong length, overflow)
    map_res(take(4u8), |bytes: &[u8]| {
        if bytes.len() == 4 {
            Ok(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
        } else {
            Err("not enough bytes")
        }
    })(input)
}
 
fn main() {
    let data = &[1, 2, 3, 4, 5, 6, 7, 8];
    
    let (rest, len) = parse_length(data).unwrap();
    println!("Length: {}", len);
    
    let (rest2, num) = parse_u32(data).unwrap();
    println!("Number: {}", num);
}

Choose map for infallible, map_res for fallible transformations.

Backtracking Behavior

use nom::{IResult, bytes::complete::tag, combinator::{map_res, alt}, error::Error};
 
fn main() {
    // map_res enables backtracking on transformation failure
    let input = "test";
    
    let parser = alt((
        map_res(tag("test"), |_| Err::<i32, &str>("fail")),
        map_res(tag("test"), |_| Ok::<i32, &str>(42)),
    ));
    
    let result: IResult<&str, i32, Error<&str>> = parser(input);
    match result {
        Ok((_, num)) => println!("Got: {}", num),  // 42
        Err(_) => println!("All alternatives failed"),
    }
    
    // First alternative: parse succeeds, transform fails
    // Backtracks to second alternative
    // Second alternative: parse succeeds, transform succeeds
    // Result: 42
}

Transformation failures trigger backtracking, enabling alternative parsing.

Error Handling Differences

use nom::{IResult, bytes::complete::tag, combinator::map_res, error::{Error, ErrorKind}};
 
fn parse_with_custom_error(input: &str) -> IResult<&str, i32, Error<&str>> {
    map_res(
        tag("number:"),
        |_s: &str| -> Result<i32, Error<&str>> {
            // Custom error handling
            Err(Error::new(input, ErrorKind::Verify))
        }
    )(input)
}
 
fn parse_with_simple_error(input: &str) -> IResult<&str, i32, Error<&str>> {
    map_res(
        tag("number:"),
        |_s: &str| -> Result<i32, &str> {
            Err("not a valid number")
        }
    )(input)
}
 
fn main() {
    let result = parse_with_simple_error("number:123");
    match result {
        Ok(_) => println!("Success"),
        Err(nom::Err::Error(e)) => println!("Error: {:?}", e),
        Err(nom::Err::Failure(e)) => println!("Failure: {:?}", e),
        Err(nom::Err::Incomplete(_)) => println!("Incomplete"),
    }
}

map_res allows custom error types for transformation failures.

Validation Use Case

use nom::{IResult, character::complete::digit1, combinator::map_res};
 
fn parse_port(input: &str) -> IResult<&str, u16, nom::error::Error<&str>> {
    // Parse digits, then validate range
    map_res(digit1, |s: &str| {
        let port: u16 = s.parse().map_err(|_| "invalid number")?;
        if port >= 1 && port <= 65535 {
            Ok(port)
        } else {
            Err("port out of range")
        }
    })(input)
}
 
fn parse_positive_int(input: &str) -> IResult<&str, i32, nom::error::Error<&str>> {
    map_res(digit1, |s: &str| {
        let num: i32 = s.parse().map_err(|_| "not a number")?;
        if num > 0 {
            Ok(num)
        } else {
            Err("must be positive")
        }
    })(input)
}
 
fn main() {
    // Valid port
    let (rest, port) = parse_port("8080/rest").unwrap();
    println!("Port: {}", port);  // 8080
    
    // Invalid port (too large)
    let result = parse_port("70000");
    assert!(result.is_err());
    
    // Negative number rejected by validation
    let result = parse_positive_int("0");
    assert!(result.is_err());
}

map_res is ideal for validation after parsing.

Type Conversion Patterns

use nom::{IResult, bytes::complete::take, combinator::map_res};
 
// Converting parsed bytes to various types
fn parse_u16_be(input: &[u8]) -> IResult<&[u8], u16> {
    map_res(take(2usize), |bytes: &[u8]| {
        Ok(u16::from_be_bytes([bytes[0], bytes[1]]))
    })(input)
}
 
fn parse_u32_be(input: &[u8]) -> IResult<&[u8], u32> {
    map_res(take(4usize), |bytes: &[u8]| {
        if bytes.len() != 4 {
            Err("wrong length")
        } else {
            Ok(u32::from_be_bytes([
                bytes[0], bytes[1], bytes[2], bytes[3]
            ]))
        }
    })(input)
}
 
fn main() {
    let data: &[u8] = &[0x01, 0x02, 0x03, 0x04, 0x05, 0x06];
    
    let (rest, num16) = parse_u16_be(data).unwrap();
    println!("u16: {}", num16);  // 258 (0x0102)
    
    let (rest2, num32) = parse_u32_be(data).unwrap();
    println!("u32: {}", num32);  // 16909060 (0x01020304)
}

Binary parsing often requires map_res for type conversions.

Chaining Transformations

use nom::{IResult, character::complete::{digit1, space1, alpha1}, 
          combinator::{map, map_res}, sequence::tuple};
 
#[derive(Debug)]
struct User {
    id: u32,
    name: String,
}
 
fn parse_user(input: &str) -> IResult<&str, User> {
    // Chain: parse tuple, then transform
    map(
        tuple((
            map_res(digit1, |s: &str| s.parse::<u32>()),
            space1,
            alpha1
        )),
        |(id, _, name)| User {
            id,
            name: name.to_string(),
        }
    )(input)
}
 
fn main() {
    let input = "42 Alice";
    let (_, user) = parse_user(input).unwrap();
    println!("User: {:?}", user);  // User { id: 42, name: "Alice" }
    
    // map_res inside tuple for fallible parse
    // map outside for infallible struct creation
}

Use map_res inside for fallible conversions, map outside for assembly.

Error Types in map_res

use nom::{IResult, bytes::complete::tag, combinator::map_res, error::{Error, ErrorKind}};
 
// Custom error type
#[derive(Debug)]
enum ParseError {
    InvalidFormat,
    ConversionError(String),
}
 
// Using different error types
fn parse_with_custom_error(input: &str) -> IResult<&str, i32, Error<&str>> {
    map_res(tag("value:"), |_| Err("my error"))(input)
}
 
// map_res error can be any type convertible to nom's error
fn parse_flexible(input: &str) -> IResult<&str, i32> {
    map_res(
        tag("num:"),
        |_| Err("conversion failed")  // Error type inferred
    )(input)
}
 
fn main() {
    let result = parse_flexible("num:");
    assert!(result.is_err());
}

map_res error types integrate with nom's error handling.

Performance Considerations

use nom::{IResult, character::complete::digit1, combinator::{map, map_res}};
 
fn main() {
    // map: always succeeds after inner parser
    // Slightly less overhead than map_res
    
    // map_res: checks Result after transformation
    // Additional branch but negligible overhead
    
    // Both are efficient; choose based on semantics, not performance
    
    // Example: string length (infallible)
    let parser1 = map(digit1, |s: &str| s.len());
    
    // Example: string to number (fallible)
    let parser2 = map_res(digit1, |s: &str| s.parse::<i32>());
    
    // Performance difference is minimal
    // Semantic correctness matters more
}

Choose based on semantics; performance difference is negligible.

Comparison Summary

use nom::{IResult, bytes::complete::tag, combinator::{map, map_res}};
 
fn main() {
    let input = "hello";
    
    // map: infallible transformation
    let parser_map = map(tag("hello"), |s: &str| s.len());
    // Type: impl Fn(&str) -> IResult<&str, usize>
    // Always succeeds if inner parser succeeds
    // Closure returns T (not Result)
    
    // map_res: fallible transformation  
    let parser_map_res = map_res(tag("hello"), |s: &str| -> Result<usize, &str> {
        if s.len() > 3 {
            Ok(s.len())
        } else {
            Err("too short")
        }
    });
    // Type: impl Fn(&str) -> IResult<&str, usize>
    // Succeeds only if inner parser AND transformation succeed
    // Closure returns Result<T, E>
    
    // Key differences:
    // 1. map closure returns T, map_res returns Result<T, E>
    // 2. map_res failure triggers backtracking
    // 3. map_res integrates with alt and other combinators
    // 4. map_res errors participate in error reporting
}

Synthesis

Core distinction:

  • map: Transformation cannot fail; always produces output if parser succeeds
  • map_res: Transformation can fail; failure becomes parser error

When to use map:

  • Type conversions that always succeed (String, Vec, struct construction)
  • Computing derived values (length, uppercase, counting)
  • Assembling parsed components into structures
  • Pure transformations with no validation

When to use map_res:

  • String to number parsing (.parse())
  • Validation after parsing (range checks, format validation)
  • Type conversions that can fail
  • Operations requiring error handling in the transformation

Backtracking integration:

  • map_res failures enable alternative parsers in alt
  • Transformation errors participate in nom's error system
  • Enables "parse and validate" as atomic operation

Error handling:

  • map_res errors can be custom types
  • Errors integrate with nom's Error and Failure variants
  • Custom error messages improve parser diagnostics

Key insight: The choice between map and map_res is about whether your transformation can fail—not about performance. Use map for infallible transformations like building structs or computing lengths. Use map_res when the transformation itself might fail, such as parsing strings to integers or validating constraints. The real power of map_res is that it integrates transformation failures with nom's backtracking system, allowing you to try alternative parsers when validation fails rather than stopping with an error.