16.6 String Conversions
Converting data to and from string representations is ubiquitous in programming, essential for I/O, serialization, configuration, and user interfaces. Rust provides standard traits for these operations.
16.6.1 Converting To Strings: Display
and ToString
The std::fmt::Display
trait is the standard way to define a user-friendly string representation for a type. Implementing Display
allows a type to be formatted using macros like println!
and format!
.
Crucially, any type implementing Display
automatically gets an implementation of the ToString
trait, which provides a to_string(&self) -> String
method.
use std::fmt; struct Complex { real: f64, imag: f64, } // Implement user-facing display format impl fmt::Display for Complex { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // Handle sign of imaginary part for nice formatting if self.imag >= 0.0 { write!(f, "{} + {}i", self.real, self.imag) } else { write!(f, "{} - {}i", self.real, -self.imag) } } } fn main() { let c1 = Complex { real: 3.5, imag: -2.1 }; let c2 = Complex { real: -1.0, imag: 4.0 }; println!("c1: {}", c1); // Uses Display implicitly println!("c2: {}", c2); let s1: String = c1.to_string(); // Uses ToString (provided by Display impl) let s2 = format!("Complex numbers are {} and {}", c1, c2); // format! also uses Display println!("String representation of c1: {}", s1); println!("{}", s2); }
16.6.2 Parsing From Strings: FromStr
and parse
The std::str::FromStr
trait defines how to parse a string slice (&str
) into an instance of a type. Many standard library types, including all primitive numeric types, implement FromStr
.
The parse()
method available on &str
delegates to the FromStr::from_str
implementation for the requested target type. Since parsing can fail (e.g., invalid format, non-numeric characters), from_str
(and therefore parse()
) returns a Result
.
use std::num::ParseIntError; fn main() { let s_valid_int = "1024"; let s_valid_float = "3.14159"; let s_invalid = "not a number"; // parse() requires the target type T to be specified or inferred // T must implement FromStr match s_valid_int.parse::<i32>() { Ok(n) => println!("Parsed '{}' as i32: {}", s_valid_int, n), Err(e) => println!("Failed to parse '{}': {}", s_valid_int, e), // e is ParseIntError } match s_valid_float.parse::<f64>() { Ok(f) => println!("Parsed '{}' as f64: {}", s_valid_float, f), Err(e) => println!("Failed to parse '{}': {}", s_valid_float, e), // e is ParseFloatError } match s_invalid.parse::<i32>() { Ok(n) => println!("Parsed '{}' as i32: {}", s_invalid, n), // Won't happen Err(e) => println!("Failed to parse '{}': {}", s_invalid, e), // Failure: invalid digit } // Using unwrap/expect for concise error handling if failure indicates a bug let num: u64 = "1234567890".parse().expect("Valid u64 string expected"); println!("Parsed u64: {}", num); }
16.6.3 Implementing FromStr
for Custom Types
Implement FromStr
for your own types to define their canonical parsing logic from strings.
use std::str::FromStr; use std::num::ParseIntError; #[derive(Debug, PartialEq)] struct RgbColor { r: u8, g: u8, b: u8, } // Define a custom error type for parsing failures #[derive(Debug, PartialEq)] enum ParseColorError { IncorrectFormat(String), // E.g., wrong number of parts InvalidComponent(ParseIntError), // Wrap the underlying integer parse error } // Implement FromStr to parse "r,g,b" format (e.g., "255, 100, 0") impl FromStr for RgbColor { type Err = ParseColorError; // Associate our custom error type fn from_str(s: &str) -> Result<Self, Self::Err> { let parts: Vec<&str> = s.trim().split(',').collect(); if parts.len() != 3 { return Err(ParseColorError::IncorrectFormat(format!( "Expected 3 comma-separated values, found {}", parts.len() ))); } // Helper closure to parse each part and map the error let parse_component = |comp_str: &str| { comp_str.trim() .parse::<u8>() .map_err(ParseColorError::InvalidComponent) // Convert ParseIntError to our error type }; let r = parse_component(parts[0])?; // Use ? for early return on error let g = parse_component(parts[1])?; let b = parse_component(parts[2])?; Ok(RgbColor { r, g, b }) } } fn main() { let input_ok = " 255, 128 , 0 "; match input_ok.parse::<RgbColor>() { Ok(color) => println!("Parsed '{}': {:?}", input_ok, color), Err(e) => println!("Error parsing '{}': {:?}", input_ok, e), } // Output: Parsed ' 255, 128 , 0 ': RgbColor { r: 255, g: 128, b: 0 } let input_bad_format = "10, 20"; match input_bad_format.parse::<RgbColor>() { Ok(color) => println!("Parsed '{}': {:?}", input_bad_format, color), Err(e) => println!("Error parsing '{}': {:?}", input_bad_format, e), } // Output: Error parsing '10, 20': // IncorrectFormat("Expected 3 comma-separated values, found 2") let input_bad_value = "10, 300, 20"; // 300 is out of range for u8 match input_bad_value.parse::<RgbColor>() { Ok(color) => println!("Parsed '{}': {:?}", input_bad_value, color), Err(e) => println!("Error parsing '{}': {:?}", input_bad_value, e), } // Output: Error parsing '10, 300, 20': InvalidComponent(ParseIntError // { kind: InvalidDigit }) (or Overflow depending on Rust version) }