Skip to content

← Errors Are Values step 15 of 24

Medium Primitives

From impls: the machinery behind ?

Parse a count and a value, using ? to convert each parser's error into your application error. Use bare ? on both parses, without map_err.

pub enum AppError { Int(ParseIntError), Float(ParseFloatError), Empty }
pub fn parse_record(line: String) -> Result<(i32, f64), AppError>

Parsing rules

Trim the line with str::trim. If nothing remains, return AppError::Empty. Otherwise split at the first comma, trim each field, and parse the left field as i32 before parsing the right field as f64.

With no comma, use the whole trimmed line as the left field and an empty right field. A valid count then produces a float error; an invalid count fails first. Additional commas stay in the right field and cause a float parse error. This format has no quoting or escaping rules.

"42,3.5" returns Ok((42, 3.5)), and " 7 , -2.25 " returns Ok((7, -2.25)). "x,y" returns an integer error. "," is an empty integer field, not an empty record.

Use the standard parsers' syntax: the count accepts decimal digits with an optional sign and must fit in i32. The float parser also accepts exponent notation and non-finite values. Do not add a finite-value check. The harness reports non-finite successful values as "NaN", "Infinity" or "-Infinity" because JSON numbers cannot represent them. Finite values remain numbers.

Keep the message and cause separate

Variant Exact Display text source()
Int(e) count field is not an integer The contained ParseIntError
Float(e) value field is not a number The contained ParseFloatError
Empty record is empty None

Keep the supplied Display implementation. Implement Error::source so callers can inspect the wrapped error instead of losing access to it through the error-chain interface. The default source() returns None; it does not delete the error stored in the enum.

The harness renders the source using its own Display implementation. Do not hard-code the standard library's error messages or append them to AppError's Display text. For example, an invalid digit and an out-of-range integer share the same application message but retain different underlying causes.

Add two From implementations

Implement From<ParseIntError> for AppError by returning AppError::Int(e), and From<ParseFloatError> for AppError by returning AppError::Float(e). Neither conversion parses anything or introduces a new failure.

In this Result-returning function, a successful parse()? yields its value. A failed parse returns early with its error converted through From into AppError. Without the corresponding conversion, the compiler reports E0277. Annotate the local bindings as i32 and f64 so each parse has a known target.

The starter's generic impl<E: Error> From<E> for AppError instead produces E0119. Since AppError implements Error, that implementation also covers E = AppError, overlapping the standard identity conversion From<T> for T. Replace it with the two concrete implementations rather than adding to it.

What the source lifetime means

source returns Option<&(dyn Error + 'static)>. The reference is borrowed from self; it need not live forever. The 'static bound applies to the underlying error type, excluding non-static borrowed data. An error can still contain a reference to static data. Both standard parse-error types fit here.

From suits this context-free wrapping. When a conversion needs a filename, field name or other information available only at one call site, an explicit conversion such as map_err may be more appropriate. Here the two field roles are already represented by the two error variants.

Your solution must compile, preserve the required messages and sources, use ? for both parses and pass Clippy with warnings treated as errors.