String Parsing with FromStr
Functional Programming
Tutorial
The Problem
Every program receives input as text — CLI arguments, config files, network packets. Converting that text into typed values safely requires consistent error handling and a standard interface. Rust's FromStr trait defines fn from_str(s: &str) -> Result<Self, Self::Err>, allowing any type to be constructed from a string with "value".parse::<MyType>(). This is the same mechanism behind "42".parse::<i32>(), "127.0.0.1".parse::<IpAddr>(), and serde_json::from_str.
🎯 Learning Outcomes
FromStr for a custom type with a custom error type.parse::<T>() to trigger FromStr through type inferenceDisplay error type that wraps the invalid input string? operator errors through map_err for ergonomic parsingFromStr, TryFrom<&str>, and serde::DeserializeCode Example
#![allow(clippy::all)]
// 473. FromStr and parse()
use std::fmt;
use std::str::FromStr;
#[derive(Debug, PartialEq)]
struct Color {
r: u8,
g: u8,
b: u8,
}
#[derive(Debug)]
struct ColorErr(String);
impl fmt::Display for ColorErr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromStr for Color {
type Err = ColorErr;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let p: Vec<&str> = s.split(',').collect();
if p.len() != 3 {
return Err(ColorErr(s.to_string()));
}
let u = |x: &str| x.trim().parse::<u8>().map_err(|_| ColorErr(s.to_string()));
Ok(Color {
r: u(p[0])?,
g: u(p[1])?,
b: u(p[2])?,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_int() {
assert_eq!("42".parse::<i32>().unwrap(), 42);
}
#[test]
fn test_err() {
assert!("abc".parse::<i32>().is_err());
}
#[test]
fn test_color() {
let c: Color = "10,20,30".parse().unwrap();
assert_eq!(
c,
Color {
r: 10,
g: 20,
b: 30
}
);
}
#[test]
fn test_bad() {
assert!("1,2".parse::<Color>().is_err());
}
}Key Differences
FromStr is a standardised trait enabling generic code (T: FromStr); OCaml uses a naming convention (of_string) without enforcement.type Err is part of the trait contract, requiring a concrete error type; OCaml of_string often returns option, losing error detail.? operator threads FromStr::Err through Result automatically; OCaml requires explicit match or Result.bind.T: FromStr (e.g., fn read_list<T: FromStr>(s: &str) -> Vec<T>); OCaml achieves this only via functors.OCaml Approach
OCaml has no built-in equivalent to FromStr. The idiomatic approach is a module-level of_string function returning option or result:
type color = { r: int; g: int; b: int }
let color_of_string s =
match String.split_on_char ',' s with
| [r; g; b] -> (
try Some { r = int_of_string (String.trim r);
g = int_of_string (String.trim g);
b = int_of_string (String.trim b) }
with Failure _ -> None)
| _ -> None
The ppx_sexp_conv library generates t_of_sexp automatically for types annotated with [@@deriving sexp], which serves a role similar to serde::Deserialize.
Full Source
#![allow(clippy::all)]
// 473. FromStr and parse()
use std::fmt;
use std::str::FromStr;
#[derive(Debug, PartialEq)]
struct Color {
r: u8,
g: u8,
b: u8,
}
#[derive(Debug)]
struct ColorErr(String);
impl fmt::Display for ColorErr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromStr for Color {
type Err = ColorErr;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let p: Vec<&str> = s.split(',').collect();
if p.len() != 3 {
return Err(ColorErr(s.to_string()));
}
let u = |x: &str| x.trim().parse::<u8>().map_err(|_| ColorErr(s.to_string()));
Ok(Color {
r: u(p[0])?,
g: u(p[1])?,
b: u(p[2])?,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_int() {
assert_eq!("42".parse::<i32>().unwrap(), 42);
}
#[test]
fn test_err() {
assert!("abc".parse::<i32>().is_err());
}
#[test]
fn test_color() {
let c: Color = "10,20,30".parse().unwrap();
assert_eq!(
c,
Color {
r: 10,
g: 20,
b: 30
}
);
}
#[test]
fn test_bad() {
assert!("1,2".parse::<Color>().is_err());
}
}
✓ Tests
Rust test suite
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_int() {
assert_eq!("42".parse::<i32>().unwrap(), 42);
}
#[test]
fn test_err() {
assert!("abc".parse::<i32>().is_err());
}
#[test]
fn test_color() {
let c: Color = "10,20,30".parse().unwrap();
assert_eq!(
c,
Color {
r: 10,
g: 20,
b: 30
}
);
}
#[test]
fn test_bad() {
assert!("1,2".parse::<Color>().is_err());
}
}
Exercises
FromStr for a struct Ipv4Addr { octets: [u8; 4] } that parses "192.168.1.1", returning a custom error for invalid octets.FromStr for struct CsvRow(Vec<String>) that splits on commas and trims whitespace from each field.Display and FromStr for Color and write a test that encodes a Color to a string and parses it back, asserting equality.