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 {
// Use write! macro to write formatted output to the formatter 'f'
write!(f, "{} + {}i", self.real, self.imag)
} else {
// Note: We use -self.imag to display a positive number after the '-' sign
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);
}
Explanation of fmt details:
f: &mut fmt::Formatter<'_>: This parameter is a mutable reference to aFormatter. ThisFormatteris essentially a destination provided by the calling context (likeprintln!,format!, etc.) where the formatted string should be written. It acts as a kind of buffer or writer abstraction. The<'_>indicates an elided lifetime, meaning theFormatterborrows something (like the underlying buffer) for a lifetime determined by the compiler, typically tied to the scope of the formatting operation.fmt::Result: This is the return type of thefmtfunction. It’s a type alias forResult<(), std::fmt::Error>. If formatting succeeds, the function returnsOk(()). If an error occurs during formatting (e.g., an I/O error if writing to a file), it returnsErr(fmt::Error).write!macro: This macro is fundamental toDisplayimplementations. It works similarly toformat!orprintln!, but instead of creating aStringor printing to the console, it writes the formatted output directly into the providedFormatter(fin this case). It returns afmt::Resultwhich is typically propagated using?or returned directly.
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; // Specific error type for integer parsing
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: NumberOutOfRange }) (Specific error may vary slightly)
}