Skip to content

← Generics and Traits step 9 of 24

Medium Primitives

From, Into, and your first blanket impl with teeth

From and Into are the standard vocabulary for infallible conversion — conversions that cannot fail, so there is no Result anywhere in sight.

The pair exists because of one impl in std:

impl<T, U> Into<U> for T
where
    U: From<T>,
{ ... }

Read that carefully. It is a blanket impl: it covers every pair of types T and U at once, on the condition that U: From<T>. So the moment you write impl From<Celsius> for Fahrenheit, the whole Into side appears by itself and celsius.into() starts working. You never write Into yourself.

Clippy enforces this, at default-on level:

error: an implementation of `From` is preferred since it gives you `Into<_>`
       for free where the reverse isn't true [clippy::from_over_into]

The starter implements Into directly, so this is the first thing you will see. The lint is the lesson, and it is not merely stylistic: only the From direction feeds the ? operator’s automatic error conversion, which is why a crate full of Into impls quietly cannot use ?.

Your task

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

impl From<Celsius> for Fahrenheit { ... }

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

F = C × 9/5 + 32. convert wraps each input in a Celsius, converts with .into(), and returns the inner f64. Keeping .into() in the body is the point: it is the observable evidence that the blanket impl fired.

These are newtypes — single-field wrappers whose job is to make two interchangeable f64s into two different types, so that passing a temperature in the wrong unit becomes a compile error. Newtypes are cheap: the wrapper has identical layout to the value inside and vanishes at runtime.

Three errors worth causing on purpose

Once your solution passes, try each of these in the editor. They are the reason this item is here.

1. Implement both. Add impl Into<Fahrenheit> for Celsius next to your working From impl:

error[E0119]: conflicting implementations of trait `Into<Fahrenheit>`
              for type `Celsius`
note: conflicting implementation in crate `core`

Your hand-written Into collides with the blanket one that already exists. There is no way to “override” a blanket impl — Rust has no stable specialisation.

2. Convert a type to itself. Write impl From<Celsius> for Celsius:

error[E0119]: conflicting implementations of trait `From<Celsius>`
              for type `Celsius`

std contains impl<T> From<T> for T — the reflexive impl. Every type can already convert to itself, so yours is a duplicate. This one genuinely confuses people, because nothing in your file mentions the conflicting impl.

3. Implement a foreign trait for a foreign type. Try impl From<Vec<u8>> for String:

error[E0117]: only traits defined in the current crate can be implemented
              for types defined outside of the crate

That is the orphan rule, and it is the reason newtypes exist at all. It gets a problem of its own later in this track.

The asymmetry to remember

  • In an impl, write From. You get Into free and ? conversion free.
  • In a bound, write Into: fn f<T: Into<Fahrenheit>>(t: T) accepts more callers than where Fahrenheit: From<T> reads. Both are satisfied by the same set of types, but the Into spelling is the conventional one at a function boundary.

That asymmetry — From in impls, Into in bounds — looks arbitrary until you have written both a few times. It is worth memorising as a rule now.

Remember the grade is compile + tests + clippy -D warnings.