ExamplesBy LevelBy TopicLearning Paths
494 Fundamental

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

  • • Convert integers to strings with .to_string() and format!("{}", n)
  • • Format in hexadecimal with format!("{:x}", n) / {:X} / {:b} / {:o}
  • • Parse a string to a number with .parse::<T>() returning Result
  • • Parse a non-decimal number with i64::from_str_radix(s, base)
  • • Format floats with precision 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

  • Error handling: Rust's 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.
  • Radix parsing: Rust's from_str_radix(s, base) takes an explicit base; OCaml's int_of_string handles 0x/0o/0b prefixes automatically.
  • Float formatting: Rust's format!("{:.2}", f) is compile-time checked; OCaml's Printf.sprintf "%.2f" f is not.
  • **Generic 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

  • Radix converter: Write fn convert_base(s: &str, from: u32, to: u32) -> Result<String, _> that parses s in base from and formats in base to.
  • Human-readable bytes: Write fn format_bytes(n: u64) -> String that formats a byte count as "1.23 MB", "456 KB", or "789 B" with appropriate precision.
  • Bulk parse: Write fn parse_csv_ints(s: &str) -> Result<Vec<i64>, _> that splits on commas, trims whitespace, and parses each field, returning the first error encountered.
  • Open Source Repos