Loading page…
Rust walkthroughs
Loading page…
hex::decode handle invalid input compared to manually parsing hex strings?The hex crate provides robust, tested hex decoding with comprehensive error handling for malformed input. Manual parsing requires explicit validation for each edge case and can easily introduce subtle bugs. Understanding these differences helps you write correct hex parsing code and know when to use each approach.
use hex::{decode, encode};
fn basic_usage() {
// Valid hex string
let bytes = decode("48656c6c6f").unwrap();
println!("{:?}", bytes); // [72, 101, 108, 108, 111]
// Round-trip
let original = "Hello";
let encoded = encode(original);
let decoded = decode(&encoded).unwrap();
assert_eq!(original.as_bytes(), decoded.as_slice());
}The decode function handles the common case cleanly.
use hex::{decode, FromHexError};
fn error_types() {
// Invalid character
match decode("4g") {
Err(FromHexError::InvalidHexCharacter { c, index }) => {
println!("Invalid char '{}' at index {}", c, index);
}
_ => {}
}
// Odd length
match decode("abc") {
Err(FromHexError::OddLength) => {
println!("Hex string has odd length");
}
_ => {}
}
// The error type provides detailed information
fn show_error(input: &str) {
match decode(input) {
Ok(bytes) => println!("Decoded: {:?}", bytes),
Err(e) => println!("Error: {}", e),
}
}
show_error("hello"); // Invalid character
show_error("abc"); // Odd length
show_error("ab"); // Ok: [171]
}FromHexError provides specific error variants for different failure modes.
use hex::{decode, FromHexError};
fn invalid_characters() {
// Lowercase and uppercase are both valid
decode("aAbBcC").unwrap(); // Valid
// Invalid characters trigger specific errors
let result = decode("ghij");
match result {
Err(FromHexError::InvalidHexCharacter { c, index }) => {
println!("Invalid char '{}' at index {}", c, index);
// Invalid char 'g' at index 0
}
_ => panic!("Unexpected result"),
}
// Non-hex characters
let cases = vec![
"ZZ", // Invalid letters
"12 34", // Space
"12-34", // Dash
"0x12ab", // 0x prefix
"12\n34", // Newline
];
for case in cases {
match decode(case) {
Err(e) => println!("{:?} -> {}", case, e),
Ok(_) => println!("{:?} -> OK", case),
}
}
}decode rejects any character outside [0-9a-fA-F].
use hex::{decode, FromHexError};
fn odd_length() {
// Hex requires even length (2 chars per byte)
assert!(decode("a").is_err());
assert!(decode("abc").is_err());
assert!(decode("abcd").is_ok());
match decode("abc") {
Err(FromHexError::OddLength) => {
println!("Odd length: 3 characters");
}
_ => {}
}
// Common mistake: forgetting to pad
let unpadded = "abc"; // 3 chars
let padded = format!("{:0>width$}", unpadded, width = unpadded.len() + unpadded.len() % 2);
// padded = "0abc"
decode(&padded).unwrap(); // Now valid
}Hex strings must have even length; odd length produces a specific error.
fn manual_parse(input: &str) -> Result<Vec<u8>, String> {
// Check length
if input.len() % 2 != 0 {
return Err("Odd length".to_string());
}
let mut bytes = Vec::with_capacity(input.len() / 2);
let chars: Vec<char> = input.chars().collect();
for i in (0..chars.len()).step_by(2) {
let high = char_to_nibble(chars[i])?;
let low = char_to_nibble(chars[i + 1])?;
bytes.push((high << 4) | low);
}
Ok(bytes)
}
fn char_to_nibble(c: char) -> Result<u8, String> {
match c {
'0'..='9' => Ok((c as u8) - b'0'),
'a'..='f' => Ok((c as u8) - b'a' + 10),
'A'..='F' => Ok((c as u8) - b'A' + 10),
_ => Err(format!("Invalid character: {}", c)),
}
}
fn test_manual() {
assert_eq!(manual_parse("48656c6c6f").unwrap(), b"Hello");
assert!(manual_parse("gh").is_err());
assert!(manual_parse("abc").is_err());
}Manual parsing requires handling all the same cases explicitly.
use hex::{decode, FromHexError};
fn error_detail_comparison() {
let input = "012g45";
// hex::decode provides rich error info
match decode(input) {
Err(FromHexError::InvalidHexCharacter { c, index }) => {
println!("hex crate: char '{}' at position {}", c, index);
// hex crate: char 'g' at position 3
}
Err(e) => println!("Other error: {}", e),
Ok(_) => {}
}
// Manual parsing typically provides less detail
fn manual_decode(s: &str) -> Result<Vec<u8>, String> {
if s.len() % 2 != 0 {
return Err("odd length".to_string());
}
let mut result = Vec::new();
for i in (0..s.len()).step_by(2) {
let byte = u8::from_str_radix(&s[i..i+2], 16)
.map_err(|e| format!("parse error: {}", e))?;
result.push(byte);
}
Ok(result)
}
match manual_decode(input) {
Err(e) => println!("Manual: {}", e),
// Manual: parse error: invalid digit found in string
_ => {}
}
}hex::decode provides the specific character and position of the error.
use hex::decode;
fn performance_comparison() {
let valid_hex = "48656c6c6f20576f726c64"; // "Hello World"
let iterations = 100_000;
// hex::decode is optimized
use std::time::Instant;
let start = Instant::now();
for _ in 0..iterations {
let _ = decode(valid_hex);
}
let hex_duration = start.elapsed();
// Manual via from_str_radix (slower due to string slicing)
let start = Instant::now();
for _ in 0..iterations {
let s = valid_hex;
let mut result = Vec::with_capacity(s.len() / 2);
for i in (0..s.len()).step_by(2) {
result.push(u8::from_str_radix(&s[i..i+2], 16).unwrap());
}
std::hint::black_box(result);
}
let manual_duration = start.elapsed();
println!("hex crate: {:?}", hex_duration);
println!("Manual: {:?}", manual_duration);
// hex crate is typically 2-3x faster
}hex::decode is optimized for the specific task.
use hex::decode;
fn case_handling() {
// Both cases are accepted
let lower = decode("deadbeef").unwrap();
let upper = decode("DEADBEEF").unwrap();
let mixed = decode("DeAdBeEf").unwrap();
// All produce the same result
assert_eq!(lower, upper);
assert_eq!(lower, mixed);
// Manual parsing must handle this explicitly
fn manual_case(s: &str) -> Result<Vec<u8>, String> {
if s.len() % 2 != 0 {
return Err("odd length".to_string());
}
let mut result = Vec::new();
for i in (0..s.len()).step_by(2) {
// Must handle both cases in char conversion
let high = s[i..i+1].to_lowercase();
let low = s[i+1..i+2].to_lowercase();
// ... additional parsing logic
}
Ok(result)
}
}decode handles both uppercase and lowercase without extra code.
use hex::{decode, FromHexError};
fn whitespace_handling() {
// hex::decode does NOT accept whitespace
let inputs = vec![
"12 34", // Space
"12\n34", // Newline
"12\t34", // Tab
"12-34", // Dash separator
"0x1234", // 0x prefix
];
for input in inputs {
match decode(input) {
Err(FromHexError::InvalidHexCharacter { c, .. }) => {
println!("{:?} rejected: invalid char '{}'", input, c);
}
Err(e) => println!("{:?} error: {}", input, e),
Ok(_) => println!("{:?} accepted", input),
}
}
// Must clean input first if it has formatting
let with_spaces = "12 34 56";
let cleaned: String = with_spaces.chars().filter(|c| !c.is_whitespace()).collect();
decode(&cleaned).unwrap(); // Now works
}decode is strict about input format; preprocessing may be needed.
use hex::{decode, FromHex};
fn decode_vs_trait() {
// Function approach
let bytes1: Vec<u8> = decode("deadbeef").unwrap();
// Trait approach - works on types directly
let bytes2: Vec<u8> = Vec::from_hex("deadbeef").unwrap();
// They're equivalent
assert_eq!(bytes1, bytes2);
// Can decode into fixed-size arrays
let array: [u8; 4] = <[u8; 4]>::from_hex("deadbeef").unwrap();
assert_eq!(array, [0xde, 0xad, 0xbe, 0xef]);
// Array decoding validates size
let result = <[u8; 4]>::from_hex("deadbeef00");
assert!(result.is_err()); // 5 bytes, not 4
}The FromHex trait provides type-directed decoding.
use hex::{decode, FromHexError};
fn real_world_handling() {
// Common real-world scenarios
// 1. Input with 0x prefix
fn decode_with_prefix(s: &str) -> Result<Vec<u8>, FromHexError> {
let hex_str = s.strip_prefix("0x").unwrap_or(s);
decode(hex_str)
}
// 2. Input with separators
fn decode_with_separators(s: &str) -> Result<Vec<u8>, FromHexError> {
let cleaned: String = s.chars()
.filter(|c| c.is_ascii_hexdigit())
.collect();
decode(&cleaned)
}
// 3. Input with validation
fn decode_validated(s: &str) -> Result<Vec<u8>, String> {
// Pre-validate for better error messages
for (i, c) in s.chars().enumerate() {
if !c.is_ascii_hexdigit() {
return Err(format!("Invalid character '{}' at position {}", c, i));
}
}
if s.len() % 2 != 0 {
return Err(format!("Odd length: {} characters", s.len()));
}
decode(s).map_err(|e| e.to_string())
}
// Usage
assert!(decode_with_prefix("0xdeadbeef").is_ok());
assert!(decode_with_separators("de-ad-be-ef").is_ok());
assert!(decode_validated("deadbeef").is_ok());
}Real-world input often requires preprocessing before decoding.
use hex::decode;
fn partial_decoding() {
// hex::decode is all-or-nothing
let input = "012345xx7890";
match decode(input) {
Err(FromHexError::InvalidHexCharacter { c, index }) => {
println!("Failed at char {} (position {})", c, index);
// No partial result available
}
_ => {}
}
// For partial decoding, you'd need custom logic
fn decode_partial(input: &str) -> (Vec<u8>, Vec<(usize, char)>) {
let mut bytes = Vec::new();
let mut errors = Vec::new();
let chars: Vec<char> = input.chars().collect();
let mut i = 0;
while i + 1 < chars.len() {
let high = chars[i].to_digit(16);
let low = chars[i + 1].to_digit(16);
match (high, low) {
(Some(h), Some(l)) => {
bytes.push((h << 4 | l) as u8);
}
_ => {
if high.is_none() {
errors.push((i, chars[i]));
}
if low.is_none() {
errors.push((i + 1, chars[i + 1]));
}
}
}
i += 2;
}
(bytes, errors)
}
let (bytes, errors) = decode_partial("0123xx78");
println!("Decoded: {:?}", bytes); // [0x01, 0x23]
println!("Errors: {:?}", errors); // [(4, 'x'), (5, 'x')]
}decode doesn't provide partial results; custom logic is needed for that.
use hex::{decode, encode};
fn encode_decode_roundtrip() {
let original = b"Hello, World!";
// Encode to hex
let hex = encode(original);
println!("Hex: {}", hex); // 48656c6c6f2c20576f726c6421
// Decode back
let decoded = decode(&hex).unwrap();
assert_eq!(original.to_vec(), decoded);
// The encode function always produces valid output
// No error type needed
let empty_hex = encode(&[]);
assert_eq!(empty_hex, "");
}encode always produces valid hex; decode must handle invalid input.
use hex::{encode, encode_upper};
fn case_output() {
let bytes = b"\xde\xad\xbe\xef";
// Default: lowercase
let lower = encode(bytes);
assert_eq!(lower, "deadbeef");
// Uppercase option
let upper = encode_upper(bytes);
assert_eq!(upper, "DEADBEEF");
// Both decode to the same value
assert_eq!(decode(&lower).unwrap(), decode(&upper).unwrap());
}Use encode_upper when uppercase output is required.
The hex::decode function handles invalid input with comprehensive error reporting:
Error types:
| Error | Description |
|-------|-------------|
| InvalidHexCharacter { c, index } | Non-hex character with position |
| OddLength | String length not divisible by 2 |
Comparison with manual parsing:
| Aspect | hex::decode | Manual parsing |
|--------|--------------|----------------|
| Error detail | Character and position | Varies by implementation |
| Case handling | Both upper and lower | Must implement explicitly |
| Performance | Optimized | Depends on implementation |
| Edge cases | All handled | Must code each one |
| Whitespace | Rejected | Must handle explicitly |
When to use hex::decode:
When preprocessing is needed:
// Strip 0x prefix
let hex_str = input.strip_prefix("0x").unwrap_or(input);
// Remove separators
let cleaned: String = input.chars()
.filter(|c| c.is_ascii_hexdigit())
.collect();
// Then decode
let bytes = decode(&cleaned)?;Key differences:
hex::decode provides precise error locationUse hex::decode for robust hex parsing with minimal boilerplate; implement manual parsing only when you need partial results or custom validation logic.