String-Number Conversion
Functional Programming
Tutorial
The Problem
Every program at its boundaries converts between numbers and text: reading config values, displaying metrics, parsing user input, serialising protocols. These conversions must handle: invalid input gracefully (Result not panic), radix selection (hex, binary, octal), floating-point precision, and negative numbers. Rust's standard library handles all these cases with a consistent Result-based API, avoiding the silent failure modes of C's atoi or Python's implicit type coercions.
🎯 Learning Outcomes
.to_string() and format!("{}", n)format!("{:x}", n) / {:X} / {:b} / {:o}.parse::<T>() returning Resulti64::from_str_radix(s, base)format!("{:.N}", f) and parse with .parse::<f64>()Code Example
#![allow(clippy::all)]
// 494. Number <-> String conversion
#[cfg(test)]
mod tests {
#[test]
fn test_to_string() {
assert_eq!(42i32.to_string(), "42");
assert_eq!((-7i32).to_string(), "-7");
}
#[test]
fn test_parse_int() {
assert_eq!("42".parse::<i32>().unwrap(), 42);
assert!("abc".parse::<i32>().is_err());
}
#[test]
fn test_hex() {
assert_eq!(format!("{:x}", 255u32), "ff");
assert_eq!(i64::from_str_radix("ff", 16).unwrap(), 255);
}
#[test]
fn test_float() {
assert_eq!(format!("{:.2}", 3.14159f64), "3.14");
assert!("3.14".parse::<f64>().is_ok());
}
}Key Differences
parse returns Result, requiring explicit error handling; OCaml's int_of_string raises Failure (exception) — use int_of_string_opt for the option-based safe version.from_str_radix(s, base) takes an explicit base; OCaml's int_of_string handles 0x/0o/0b prefixes automatically.format!("{:.2}", f) is compile-time checked; OCaml's Printf.sprintf "%.2f" f is not.parse**: Rust's .parse::<T>() is generic over any T: FromStr; OCaml requires type-specific functions (int_of_string, float_of_string).OCaml Approach
(* int to string *)
string_of_int 42 (* "42" *)
Printf.sprintf "%d" 42 (* "42" *)
(* hex *)
Printf.sprintf "%x" 255 (* "ff" *)
(* string to int *)
int_of_string "42" (* 42 — raises Failure on error *)
int_of_string_opt "42" (* Some 42 — OCaml 4.05+ *)
int_of_string "0xff" (* 255 — handles 0x prefix *)
(* float *)
string_of_float 3.14 (* "3.14" *)
float_of_string "3.14" (* 3.14 *)
Full Source
#![allow(clippy::all)]
// 494. Number <-> String conversion
#[cfg(test)]
mod tests {
#[test]
fn test_to_string() {
assert_eq!(42i32.to_string(), "42");
assert_eq!((-7i32).to_string(), "-7");
}
#[test]
fn test_parse_int() {
assert_eq!("42".parse::<i32>().unwrap(), 42);
assert!("abc".parse::<i32>().is_err());
}
#[test]
fn test_hex() {
assert_eq!(format!("{:x}", 255u32), "ff");
assert_eq!(i64::from_str_radix("ff", 16).unwrap(), 255);
}
#[test]
fn test_float() {
assert_eq!(format!("{:.2}", 3.14159f64), "3.14");
assert!("3.14".parse::<f64>().is_ok());
}
}
✓ Tests
Rust test suite
#[cfg(test)]
mod tests {
#[test]
fn test_to_string() {
assert_eq!(42i32.to_string(), "42");
assert_eq!((-7i32).to_string(), "-7");
}
#[test]
fn test_parse_int() {
assert_eq!("42".parse::<i32>().unwrap(), 42);
assert!("abc".parse::<i32>().is_err());
}
#[test]
fn test_hex() {
assert_eq!(format!("{:x}", 255u32), "ff");
assert_eq!(i64::from_str_radix("ff", 16).unwrap(), 255);
}
#[test]
fn test_float() {
assert_eq!(format!("{:.2}", 3.14159f64), "3.14");
assert!("3.14".parse::<f64>().is_ok());
}
}
Exercises
fn convert_base(s: &str, from: u32, to: u32) -> Result<String, _> that parses s in base from and formats in base to.fn format_bytes(n: u64) -> String that formats a byte count as "1.23 MB", "456 KB", or "789 B" with appropriate precision.fn parse_csv_ints(s: &str) -> Result<Vec<i64>, _> that splits on commas, trims whitespace, and parses each field, returning the first error encountered.