Skip to content

← Generics and Traits step 9 of 24

Medium Primitives

Convert temperatures with From and Into

Replace the starter's direct Into implementation with a From implementation, keeping the supplied convert function unchanged.

pub struct Celsius(pub f64);
pub struct Fahrenheit(pub f64);

impl From<Celsius> for Fahrenheit { /* implement from */ }

pub fn convert(temps: Vec<f64>) -> Vec<f64>

Use C * 9.0 / 5.0 + 32.0 to convert Celsius to Fahrenheit. Return one result per input, preserving order and duplicates. Empty input returns an empty vector. Do not round the results to a fixed number of decimal places.

Input Output
[0.0, 100.0] [32.0, 212.0]
[-40.0] [-40.0]
[37.0, 20.5] [98.6, 68.9]
[] []

Inputs are finite f64 values whose intermediate multiplication by 9 and final result remain finite. This exercise applies the formula even below absolute zero; it does not validate physical temperatures. Floating-point arithmetic may round internally, so infallible does not mean exact decimal arithmetic.

Why implement From?

From<Celsius> for Fahrenheit defines a conversion on the destination type. Its method is fn from(c: Celsius) -> Self.

The standard library provides Into<U> for T whenever U: From<T>. This blanket implementation means your From implementation also enables Celsius(t).into(). The destination type in let f: Fahrenheit = ... tells Rust which conversion is wanted.

The starter's direct Into implementation computes the right values, but Clippy's from_over_into lint rejects it under warnings-as-errors. Replace that implementation, rather than adding From beside it. Keep the formula and the existing .into() call in convert.

Two types for two units

Celsius and Fahrenheit are newtypes: distinct types wrapping the same underlying numeric type. A function expecting Fahrenheit will not silently accept Celsius. Conversion must be requested explicitly, for example with Fahrenheit::from(c) or a type-directed c.into().

These wrappers introduce no heap allocation themselves. Their default Rust representation does not promise an ABI identical to f64; #[repr(transparent)] is the representation attribute used when that guarantee is needed. No representation change is required for this exercise.

Avoid conflicting implementations

Adding both From<Celsius> for Fahrenheit and Into<Fahrenheit> for Celsius produces E0119: the explicit Into overlaps the blanket implementation. Similarly, a custom From<Celsius> for Celsius conflicts with the standard identity conversion, From<T> for T.

Your local wrapper types also let you implement standard traits for them. An implementation such as From<Vec<u8>> for String is rejected with E0117 because the trait and both involved types belong to other crates. Wrapping a type locally is useful when you need your own conversion behavior.

From implementations and Into bounds

Prefer implementing From when defining a conversion. An API accepting T: Into<Fahrenheit> can then accept any T with the corresponding From implementation, as well as types that implement Into directly. The reverse implication does not hold: a direct Into implementation does not create a From implementation.

For ordinary Result error propagation, ? uses a From conversion when the source and destination error types differ. That is a separate use of the trait; these temperature wrappers are not error types. If a conversion can reject its input, use a fallible interface such as TryFrom rather than hiding that failure inside a From implementation.

Your solution must compile, preserve convert, match the outputs and pass Clippy with warnings treated as errors.