Step 3 of 7
FromStr: parse into your own type
"42".parse::<i32>() works because i32 implements the trait std::str::FromStr. Implement it for your own type and .parse() works for it too. This is the idiomatic way to turn text (config files, command-line arguments, user input) into a value.
use std::str::FromStr;
#[derive(Debug)]
struct Setting {
key: String,
value: i64,
}
impl FromStr for Setting {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (key, value) = s.split_once('=').ok_or(format!("missing '=' in {s:?}"))?;
let key = key.trim();
if key.is_empty() {
return Err(String::from("empty key"));
}
let value = value.trim().parse::<i64>().map_err(|_| format!("bad number {value:?}"))?;
Ok(Setting { key: key.to_string(), value })
}
}
fn main() {
let s: Setting = "volume = 7".parse().unwrap();
println!("{} -> {}", s.key, s.value);
println!("{:?}", "volume".parse::<Setting>());
println!("{:?}", "=3".parse::<Setting>());
println!("{:?}", "lives=lots".parse::<Setting>());
}
volume -> 7
Err("missing '=' in \"volume\"")
Err("empty key")
Err("bad number \"lots\"")
Piece by piece:
type Err = String;is the error type. It's spelledErrhere (TryFromusesError): an old naming accident you just have to remember.fn from_str(s: &str) -> Result<Self, Self::Err>does the parsing.s.split_once('=')splits at the first=and returnsSome((before, after)), orNoneif there's no=..ok_or(...)turns thatOptioninto aResult, and?returns the error early..map_err(|_| ...)replaces theParseIntErrorwith a friendlier message. The|_| ...is a small inline function (a closure, a later module) that ignores the original error.- You don't call
from_stryourself: write.parse::<Setting>(), or.parse()with the type annotated on the variable.
Why not a fn parse_setting(s: &str)?
It would work, but FromStr plugs into the rest of the ecosystem. Code that reads "any value that can be parsed" (T: FromStr) works with your type automatically: command-line parsers like clap, config loaders, and your own generic helpers.
Common mistakes
type Errorinstead oftype Errgiveserror[E0437]: type `Error` is not a member of trait `FromStr`.- Forgetting
use std::str::FromStr;.FromStrisn't in the prelude, so theimplline can't find it (or writeimpl std::str::FromStr for ...). unwrap()on user input. It's fine in a quick example; in real code, handle theErr(withmatchor?), because users will typelots.
Your task
Implement FromStr for Dimensions so that "3x4".parse::<Dimensions>() gives Ok(Dimensions { width: 3, height: 4 }). Allow spaces around the numbers (" 10 x 20 "). Return an Err (any String message) if there's no x or if either side isn't a whole number.
Challenge 2
Implement FromStr for Color so that web colors like "#ff8800" parse into Color { r: 255, g: 136, b: 0 }. The text must be exactly 7 characters: a # followed by 6 hex digits (upper or lower case). Anything else is an Err.
u8::from_str_radix(text, 16) parses a hex number such as "ff" into 255 (it returns a Result, like parse). Slice out the pairs with &s[1..3], &s[3..5] and &s[5..7].
Challenge 3
A robot reads commands, one per line from stdin. Implement FromStr for Command:
forward <n>becomesCommand::Forward(n)(n is au32)turn left/turn rightbecomeCommand::Turn(Side::Left)/Command::Turn(Side::Right)stopbecomesCommand::Stop- anything else is an
Errwith the messageunknown command: <line>
Then main (already written) parses every line and prints what the robot does, or error: <message>.