From 84231621595635599edd323824e9eeb0c1fd1465 Mon Sep 17 00:00:00 2001 From: Joshua Isika Date: Thu, 6 Aug 2026 13:50:38 +0300 Subject: [PATCH] decimal: replace try_from with FromStr impl Implements exercism/rust#1037. Replaces the custom Decimal::try_from method with `impl FromStr for Decimal`, so students (and test/example code) parse with `.parse()` instead of a bespoke constructor. Updates the example solution, student stub, and test file accordingly. All 44 exercise tests and clippy pass against the updated example. --- exercises/practice/decimal/.meta/example.rs | 82 ++++++++++++--------- exercises/practice/decimal/src/lib.rs | 9 ++- exercises/practice/decimal/tests/decimal.rs | 4 +- 3 files changed, 56 insertions(+), 39 deletions(-) diff --git a/exercises/practice/decimal/.meta/example.rs b/exercises/practice/decimal/.meta/example.rs index a11f60f8f16..fea873ccb38 100644 --- a/exercises/practice/decimal/.meta/example.rs +++ b/exercises/practice/decimal/.meta/example.rs @@ -1,6 +1,7 @@ use std::cmp::Ordering; use std::fmt; use std::ops::{Add, Mul, Sub}; +use std::str::FromStr; extern crate num_bigint; use num_bigint::BigInt; @@ -24,39 +25,6 @@ impl Decimal { value } - pub fn try_from(mut input: &str) -> Option { - // clear extraneous whitespace - input = input.trim(); - - // don't bother to trim extraneous zeroes - // leave it to users to manage their own memory - - // now build a representation of the number to parse - let mut digits = String::with_capacity(input.len()); - let mut decimal_index = None; - for ch in input.chars() { - match ch { - '0'..='9' | '-' | '+' => { - digits.push(ch); - if let Some(idx) = decimal_index.as_mut() { - *idx += 1; - } - } - '.' => { - if decimal_index.is_some() { - return None; - } - decimal_index = Some(0) - } - _ => return None, - } - } - Some(Decimal::new( - digits.parse().ok()?, - decimal_index.unwrap_or_default(), - )) - } - /// Add precision to the less-precise value until precisions match /// /// Precision, in this case, is defined as the decimal index. @@ -94,6 +62,47 @@ impl Decimal { } } +/// Indicates that a string could not be parsed as a `Decimal` +#[derive(Debug, PartialEq, Eq)] +pub struct ParseDecimalError; + +impl FromStr for Decimal { + type Err = ParseDecimalError; + + fn from_str(input: &str) -> Result { + // clear extraneous whitespace + let input = input.trim(); + + // don't bother to trim extraneous zeroes + // leave it to users to manage their own memory + + // now build a representation of the number to parse + let mut digits = String::with_capacity(input.len()); + let mut decimal_index = None; + for ch in input.chars() { + match ch { + '0'..='9' | '-' | '+' => { + digits.push(ch); + if let Some(idx) = decimal_index.as_mut() { + *idx += 1; + } + } + '.' => { + if decimal_index.is_some() { + return Err(ParseDecimalError); + } + decimal_index = Some(0) + } + _ => return Err(ParseDecimalError), + } + } + Ok(Decimal::new( + digits.parse().map_err(|_| ParseDecimalError)?, + decimal_index.unwrap_or_default(), + )) + } +} + macro_rules! auto_impl_decimal_ops { ($(#[$attr:meta])* $trait:ident, $func_name:ident, $digits_operation:expr, $index_operation:expr) => { impl $trait for Decimal { @@ -179,11 +188,14 @@ mod tests { println!( "Decimal representation of \"{}\": {}", test_str, - Decimal::try_from(test_str).expect("This should always become a decimal") + test_str + .parse::() + .expect("This should always become a decimal") ); assert_eq!( test_str, - Decimal::try_from(test_str) + test_str + .parse::() .expect("This should always become a decimal") .to_string() ) diff --git a/exercises/practice/decimal/src/lib.rs b/exercises/practice/decimal/src/lib.rs index c9ad7098f33..8c6a0db404e 100644 --- a/exercises/practice/decimal/src/lib.rs +++ b/exercises/practice/decimal/src/lib.rs @@ -1,10 +1,15 @@ +use std::str::FromStr; + /// Type implementing arbitrary-precision decimal arithmetic pub struct Decimal { // implement your type here } -impl Decimal { - pub fn try_from(input: &str) -> Option { +impl FromStr for Decimal { + // implement your error type here + type Err = String; + + fn from_str(input: &str) -> Result { todo!("Create a new decimal with a value of {input}") } } diff --git a/exercises/practice/decimal/tests/decimal.rs b/exercises/practice/decimal/tests/decimal.rs index ebdf9d7d509..dc7cf5a517e 100644 --- a/exercises/practice/decimal/tests/decimal.rs +++ b/exercises/practice/decimal/tests/decimal.rs @@ -4,7 +4,7 @@ use decimal::Decimal; /// /// Use only when you _know_ that your value is valid. fn decimal(input: &str) -> Decimal { - Decimal::try_from(input).expect("That was supposed to be a valid value") + input.parse().expect("That was supposed to be a valid value") } /// Some big and precise values we can use for testing. [0] + [1] == [2] @@ -185,7 +185,7 @@ fn gt_varying_negative_precisions() { #[test] #[ignore] fn negatives() { - assert!(Decimal::try_from("-1").is_some()); + assert!("-1".parse::().is_ok()); assert_eq!(decimal("0") - decimal("1"), decimal("-1")); assert_eq!(decimal("5.5") + decimal("-6.5"), decimal("-1")); }