I'm trying to apply this design pattern. In some languages (such as PHP), we'd use e.g. a withX
method which returns a clone of the original object with the requested x
property.
Am I doing this right?
use err_derive::Error;
use lazy_static::lazy_static;
use regex::Regex;
use std::fmt;
use std::io::{self, BufRead};
use std::num::ParseFloatError;
use std::process;
use std::str::FromStr;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum TemperatureUnit {
Celcius,
Fahrenheit,
}
impl TemperatureUnit {
fn symbol(&self) -> &str {
match *self {
TemperatureUnit::Celcius => "C",
TemperatureUnit::Fahrenheit => "F",
}
}
}
#[derive(Debug, PartialEq)]
struct Temperature {
value: f32,
unit: TemperatureUnit,
}
impl Temperature {
fn convert(&self, unit: TemperatureUnit) -> Self {
if unit == self.unit {
return Self { ..*self };
}
const CELCIUS_TO_FAHRENHEIT_RATIO: f32 = 1.8;
const CELCIUS_TO_FAHRENHEIT_OFFSET: u8 = 32;
let value = match (&self.unit, &unit) {
(TemperatureUnit::Celcius, TemperatureUnit::Fahrenheit) => {
self.value * CELCIUS_TO_FAHRENHEIT_RATIO + f32::from(CELCIUS_TO_FAHRENHEIT_OFFSET)
},
(TemperatureUnit::Fahrenheit, TemperatureUnit::Celcius) => {
(self.value - f32::from(CELCIUS_TO_FAHRENHEIT_OFFSET)) / CELCIUS_TO_FAHRENHEIT_RATIO
},
_ => unreachable!(),
};
Self { value, unit }
}
}
impl FromStr for Temperature {
type Err = ParseTemperatureError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
return Err(ParseTemperatureError::Empty);
}
const TEMPERATURE_PATTERN: &str = r"(?i)(?P<value>\d+(?:\.\d+)?)\s?(?P<unit>C|F)";
lazy_static! {
static ref TEMPERATURE_REGEX: Regex = Regex::new(TEMPERATURE_PATTERN).unwrap();
}
let caps = match TEMPERATURE_REGEX.captures(s) {
Some(caps) => caps,
None => {
return Err(ParseTemperatureError::Invalid);
},
};
let value: f32 = match (&caps["value"]).parse() {
Ok(v) => v,
Err(err) => {
return Err(ParseTemperatureError::Parse(err));
},
};
let unit = match &caps["unit"] {
"C" | "c" => TemperatureUnit::Celcius,
"F" | "f" => TemperatureUnit::Fahrenheit,
_ => unreachable!(),
};
Ok(Temperature { value, unit })
}
}
impl fmt::Display for Temperature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", self.value, self.unit.symbol())
}
}
#[derive(Debug, Error)]
enum ParseTemperatureError {
#[error(display = "cannot parse temperature from empty string")]
Empty,
#[error(display = "invalid temperature literal")]
Invalid,
#[error(display = "cannot parse temperature value: {:?}", _0)]
Parse(#[error(source)] ParseFloatError),
}
fn main() {
const DEFAULT_INPUT: &str = "36.9C";
println!("Temperature [{}]:", DEFAULT_INPUT);
let input = match io::stdin().lock().lines().next() {
Some(Ok(line)) => line,
Some(Err(err)) => panic!("Failed to read line: {:?}", err),
None => {
eprintln!("No input");
process::exit(1);
},
};
let input = if input.is_empty() { DEFAULT_INPUT } else { input.trim() };
let input: Temperature = match input.parse() {
Ok(t) => t,
Err(err) => {
eprintln!("Invalid input: {}", err);
process::exit(1);
},
};
let output = input.convert(match input.unit {
TemperatureUnit::Celcius => TemperatureUnit::Fahrenheit,
TemperatureUnit::Fahrenheit => TemperatureUnit::Celcius,
});
println!("{} = {}", input, output);
}