Rust: invalid digit found in string
Fix Rust parse errors "invalid digit found in string" and "cannot parse integer" when input contains whitespace, signs, or non-numeric characters.
Symptoms
- ParseIntError { kind: InvalidDigit }
- Parsing "12\n" or " 42" fails
- Deserialization of a numeric string into an integer fails
Possible causes
- The string includes a trailing newline from a file read
- Leading or trailing spaces were not trimmed
- A thousands separator or currency symbol is present
- A hexadecimal value is parsed as base 10
Diagnosis steps
Print the raw value with escaping
println!("{:?}", raw);Interpretation
The debug format reveals hidden whitespace and control characters that display normally in a println.
Trim before parsing
raw.trim().parse::<i64>()Interpretation
If trimming fixes it, the input simply carried surrounding whitespace.
Check the numeric base
Interpretation
Values like 0xFF or "deadbeef" need from_str_radix with the right base.
Root cause
Rust's FromStr for integers accepts an optional sign followed by digits in the chosen base, and nothing else. A newline, space, comma, or letter makes the whole string invalid, so the parser returns InvalidDigit rather than skipping the stray character.
Fix
Trim input, strip common separators deliberately, and choose the right base with i64::from_str_radix(s, 16). When the source mixes formats, parse into a richer type or validate with a regex first. Propagate the ParseIntError with context so the failing value is visible in logs.
Verify the fix
Re-parse the same input after trimming; the value converts successfully, and a test asserting that " 42\n".trim().parse::<i64>() equals Ok(42) locks in the behaviour.
Symptoms
Parsing fails with ParseIntError { kind: InvalidDigit } or cannot parse integer from empty string. The visible value often looks numeric, which means a hidden character is present.
Diagnosis
Print with debug formatting to expose whitespace:
let raw = "42\n";
println!("{raw:?}"); // "42\n"
let n: i64 = raw.trim().parse()?;
Check the base for hex or other radices.
Fix
Trim and choose the right base:
let n: i64 = raw.trim().parse()?;
let hex = i64::from_str_radix("ff", 16)?; // 255
Normalise separators before parsing when a source mixes thousands separators.
Verification
Re-run with the previous input; parsing succeeds. A unit test asserts " 42\n".trim().parse::<i64>() == Ok(42) so the trim requirement is documented and enforced.
Where the Stray Bytes Come From
Input rarely arrives clean. Files end each line with a newline that read_line keeps, environment variables can carry a trailing carriage return on Windows, and CLI arguments may include the whole quoted string. Reading with lines() strips the terminator automatically, which is why switching from read_to_string plus manual splitting to an iterator often removes the bug entirely. For data that mixes formats, parse into a domain type whose constructor performs the normalisation once, so every call site inherits the same guarantee instead of repeating a fragile trim.