How does nom::character::complete::char differ from one_of for single character matching?
char matches exactly one specific character, while one_of matches any character from a set of allowed characters, making char more restrictive and one_of more flexible for character class matching. Both are parser combinators in nom that consume input when successful, but they serve different use cases in grammar definitions.
The char Function
use nom::character::complete::char;
use nom::error::Error;
fn char_example() {
// char matches exactly one specific character
let input = "hello";
// Match 'h' exactly
let result: Result<(&str, char), nom::Err<Error<&str>>> = char('h')(input);
assert_eq!(result, Ok(("ello", 'h')));
// Match fails if character doesn't match
let result = char('x')(input);
assert!(result.is_err());
}char takes a single character and succeeds only if the input starts with that exact character.
The one_of Function
use nom::character::complete::one_of;
use nom::error::Error;
fn one_of_example() {
// one_of matches any character from the given set
let input = "hello";
// Match any vowel
let result: Result<(&str, char), nom::Err<Error<&str>>> = one_of("aeiou")(input);
assert_eq!(result, Ok(("ello", 'h'))); // Wait, 'h' is not a vowel...
// Actually, this would fail:
let input = "ello"; // Starts with 'e'
let result = one_of("aeiou")(input);
assert_eq!(result, Ok(("llo", 'e')));
// Match any digit
let input = "123abc";
let result = one_of("0123456789")(input);
assert_eq!(result, Ok(("23abc", '1')));
}one_of takes a string of allowed characters and matches any one of them.
Matching Specificity
use nom::character::complete::{char, one_of};
use nom::sequence::preceded;
use nom::combinator::map;
fn specificity() {
// char: Exactly one character
let parse_a = char('a');
// Only matches 'a', nothing else
// one_of: Any character from set
let parse_vowel = one_of("aeiou");
// Matches 'a', 'e', 'i', 'o', or 'u'
// char is more restrictive
// one_of is more flexible
// For single character, char is clearer:
let input = "abc";
assert_eq!(char('a')(input), Ok(("bc", 'a')));
// one_of for the same purpose works but is less clear:
assert_eq!(one_of("a")(input), Ok(("bc", 'a')));
// This is equivalent but confusing - why use a set for one character?
}Use char for single-character matching and one_of for character classes.
Return Types
use nom::character::complete::{char, one_of};
use nom::error::Error;
fn return_types() {
// Both return the matched character
let input = "abc";
let result1: Result<(&str, char), _> = char('a')(input);
let result2: Result<(&str, char), _> = one_of("a")(input);
// Both produce char in the successful output
assert_eq!(result1.unwrap().1, 'a');
assert_eq!(result2.unwrap().1, 'a');
// The return type is the same:
// IResult<&str, char> for both
}Both functions return the matched character with identical types.
Performance Characteristics
use nom::character::complete::{char, one_of};
fn performance() {
// char: Direct comparison O(1)
// Single comparison against input
// one_of: Set lookup O(n) or O(1) depending on optimization
// Checks if input character is in the set
// For most cases, the difference is negligible
// Both are very fast
// char may be slightly faster for single character:
// - One comparison
// - No loop or set lookup
// one_of with many characters:
// - May iterate through set
// - Or use optimized matching
let digit_parser = one_of("0123456789");
// In practice, both are fast enough for text parsing
}char has slightly better performance for single-character matching.
Character Classes
use nom::character::complete::one_of;
use nom::multi::many1;
use nom::sequence::tuple;
fn character_classes() {
// one_of excels at character classes
// Match any hexadecimal digit
let hex_digit = one_of("0123456789abcdefABCDEF");
// Match any whitespace
let whitespace = one_of(" \t\n\r");
// Match operators
let operator = one_of("+-*/%");
// These would be tedious with char:
let hex_digit_alt = |input| {
// Would need: char('0') | char('1') | char('2') | ... | char('F')
// Very verbose and less efficient
unimplemented!()
};
}one_of is ideal for character classes and sets.
Combining with Other Parsers
use nom::character::complete::{char, one_of, digit0};
use nom::sequence::{preceded, delimited, tuple};
use nom::multi::many0;
use nom::branch::alt;
fn combining_parsers() {
let input = "a1,b2,c3";
// char for specific delimiters
let parse_pair = tuple((
one_of("abc"), // Letter from set
digit0, // Digits
char(','), // Specific delimiter
));
// char for exact matches in grammar
let parenthesized = delimited(char('('), digit0, char(')'));
// one_of for flexible matching
let operators = one_of("+-*/");
// Combinations work naturally
let parse_expr = tuple((
one_of("xyz"),
char('='),
many0(one_of("0123456789"))
));
}Both integrate seamlessly with nom's combinators.
Error Messages
use nom::character::complete::{char, one_of};
use nom::error::{Error, ErrorKind};
fn error_messages() {
let input = "xyz";
// char error
let err1 = char('a')(input);
// Error: expected 'a', found 'x'
// one_of error
let err2 = one_of("abc")(input);
// Error: expected one of "abc", found 'x'
// Both produce ErrorKind::Char
// Error messages may differ slightly in display
}Both produce similar errors; one_of may include the character set in error context.
Complete Module Examples
use nom::character::complete::{char, one_of, digit1, alpha1};
use nom::sequence::tuple;
use nom::multi::many0;
use nom::combinator::map;
use nom::branch::alt;
// Parsing a simple identifier: letter followed by alphanumeric
fn parse_identifier(input: &str) -> nom::IResult<&str, String> {
let (input, first) = one_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_")(input)?;
let (input, rest) = many0(one_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789"))(input)?;
Ok((input, std::iter::once(first).chain(rest).collect()))
}
// Parsing JSON-like values
fn parse_json_value(input: &str) -> nom::IResult<&str, String> {
// Using char for exact matches
let (input, _) = char('"')(input)?;
let (input, content) = many0(one_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ "))(input)?;
let (input, _) = char('"')(input)?;
Ok((input, content.into_iter().collect()))
}
// Parsing operators
fn parse_operator(input: &str) -> nom::IResult<&str, &str> {
use nom::bytes::complete::tag;
use nom::branch::alt;
// char for single-char operators
// one_of could work but tag is clearer for multi-char
alt((
tag("=="), tag("!="), tag("<="), tag(">="),
tag("+"), tag("-"), tag("*"), tag("/"),
))(input)
}
fn main_examples() {
let input = "hello";
// char: exact match
assert_eq!(char('h')(input), Ok(("ello", 'h')));
// one_of: character class
assert_eq!(one_of("helo")(input), Ok(("ello", 'h')));
// Parsing identifier
let (rem, id) = parse_identifier("var123").unwrap();
assert_eq!(id, "var123");
assert_eq!(rem, "");
}none_of Complement
use nom::character::complete::{char, one_of, none_of};
fn complement() {
// none_of is the complement of one_of
// It matches any character NOT in the set
let input = "hello";
// Match character that's NOT a vowel
let result = none_of("aeiou")(input);
assert_eq!(result, Ok(("ello", 'h'))); // 'h' is not a vowel
// This is useful for parsing until a delimiter
let input = "hello,world";
let result = none_of(",")(input);
assert_eq!(result, Ok(("ello,world", 'h'))); // Stops at comma? No!
// Actually, none_of only matches one character
// To match multiple, use many0(none_of(","))
use nom::multi::many0;
let result = many0(none_of(","))(input);
assert_eq!(result, Ok((",world", vec
!['h','e','l','l','o']));
}none_of is the complement of one_of, useful for exclusion-based matching.
Parsing Quoted Strings
use nom::character::complete::{char, none_of};
use nom::bytes::complete::escaped;
use nom::multi::many0;
fn quoted_string() {
// Common pattern: parse string content until quote
// Use none_of to match non-quote characters
fn parse_string_content(input: &str) -> nom::IResult<&str, String> {
let (input, _) = char('"')(input)?;
let (input, chars) = many0(none_of("\""))(input)?;
let (input, _) = char('"')(input)?;
Ok((input, chars.into_iter().collect()))
}
let result = parse_string_content("\"hello\"");
assert_eq!(result, Ok(("", "hello".to_string())));
// char for the delimiters, none_of for content
}Combine char for delimiters with none_of for content matching.
Real-World Grammar Example
use nom::character::complete::{char, one_of, digit1, alpha1};
use nom::sequence::{preceded, delimited, tuple};
use nom::multi::{many0, separated_list0};
use nom::branch::alt;
use nom::combinator::map;
fn parse_token(input: &str) -> nom::IResult<&str, Token> {
// Use char for punctuation
let punctuation = alt((
map(char('{'), |_| Token::LBrace),
map(char('}'), |_| Token::RBrace),
map(char('('), |_| Token::LParen),
map(char(')'), _| Token::RParen),
map(char(';'), |_| Token::Semicolon),
map(char(','), |_| Token::Comma),
));
// Use one_of for character classes
let operator = map(one_of("+-*/=<>!"), |c| Token::Operator(c));
let number = map(digit1, |s: &str| Token::Number(s.parse().unwrap()));
let identifier = map(alpha1, |s: &str| Token::Ident(s.to_string()));
alt((punctuation, operator, number, identifier))(input)
}
#[derive(Debug)]
enum Token {
LBrace, RBrace, LParen, RParen, Semicolon, Comma,
Operator(char),
Number(i32),
Ident(String),
}
fn real_world_example() {
// char: Exact punctuation matching
// one_of: Flexible operator matching
// Both produce clear parser code
}Real grammars use both: char for exact punctuation, one_of for character classes.
Comparison Table
fn comparison() {
// ┌─────────────────────────────────────────────────────────────────────────┐
// │ Aspect │ char │ one_of │
// ├─────────────────────────────────────────────────────────────────────────┤
// │ Input │ Single char │ String of chars │
// │ Match │ Exact character │ Any in set │
// │ Flexibility │ Low (exact) │ High (set) │
// │ Performance │ Slightly faster │ Slightly slower │
// │ Use case │ Punctuation, keywords │ Character classes │
// │ Clarity │ Clear intent │ Flexible matching │
// │ Error context │ Shows expected char │ Shows character set │
// └─────────────────────────────────────────────────────────────────────────┘
}Summary
fn summary() {
// ┌─────────────────────────────────────────────────────────────────────────┐
// │ Rule │ When to use │
// ├─────────────────────────────────────────────────────────────────────────┤
// │ char('x') │ - Exact single character match │
// │ │ - Punctuation (parentheses, braces) │
// │ │ - Keywords with single char │
// │ │ - Delimiters │
// ├─────────────────────────────────────────────────────────────────────────┤
// │ one_of("abc") │ - Character classes │
// │ │ - Multiple allowed characters │
// │ │ - Flexible matching │
// │ │ - Alternative characters │
// └─────────────────────────────────────────────────────────────────────────┘
// Key points:
// 1. char matches exactly one character
// 2. one_of matches any character from a set
// 3. Both return the matched character
// 4. char is clearer for single-character expectations
// 5. one_of is essential for character classes
// 6. none_of is the complement of one_of
// 7. Both have similar error handling
// 8. Use char for punctuation, one_of for flexible matching
}Key insight: The distinction between char and one_of reflects a fundamental parser design principle: use the most restrictive parser that correctly matches your grammar. char expresses the intent "I expect exactly this character" while one_of expresses "I expect any of these characters." This clarity improves parser readability and error messages. For punctuation like parentheses, semicolons, and delimiters, char is the natural choice. For character classes like digits (one_of("0123456789")), letters (one_of("abcdefghijklmnopqrstuvwxyz")), or operators (one_of("+-*/")), one_of provides the necessary flexibility. Both functions integrate seamlessly with nom's combinator system and return identical types, making them interchangeable in most contexts where the match semantics align.
