Skip to content

← Shape Your Data: Structs, Enums, Pattern Matching step 3 of 26

Easy Primitives

Convert distances with tuple and unit structs

Repair the feet-to-metres conversion in this function:

pub fn normalise_distances(values: Vec<f64>, units: Vec<String>) -> Vec<f64>

Pair each value with the unit at the same position and return converted values in that order. Stop at the shorter vector. If either input is empty, return an empty vector.

Unit string Conversion
m Keep the value
ft Multiply by 0.3048
Anything else Return 0.0 for that pair

Match unit strings exactly. Uppercase or whitespace-padded strings are unrecognized. Negative measurements are allowed and retain their sign for recognized units. Inputs are finite floating-point values. Return the computed values without extra rounding; grading uses a numeric tolerance.

For example, values [10.0, -3.0, 8.0] with units ["m", "ft", "yd"] produce [10.0, -0.9144, 0.0]. One foot is defined as 0.3048 metres, but storing that factor and multiplying in f64 involves floating-point rounding.

The starter already handles pairing, unknown units and collecting the results. Fix the field access inside From<Feet> for Meters while keeping the provided types and conversion structure.

Tuple structs give values distinct types

struct Meters(f64);
struct Feet(f64);

These are two distinct types, each containing one f64 field. This use of a single-field wrapper is called the newtype pattern. A function expecting Meters cannot directly accept Feet, even though their fields have the same numeric type.

Fields in tuple structs are numbered. Read the first field as feet.0. The starter uses feet.value, but no field named value exists, so it fails with E0609.

The types prevent an accidental interchange at typed call sites. They do not verify the conversion factor, reject negative values, or validate the incoming unit strings automatically. Those behaviors come from your code. The default struct representation also supplies no general foreign-function ABI promise; a type distinction and a layout guarantee are separate matters.

From also supplies Into in the same direction

Implementing From<Feet> for Meters supports both Meters::from(feet) and feet.into() when the destination type is known. Both convert feet to metres. Neither supplies the reverse conversion.

In this exercise the implementation reads feet.0, multiplies it by the factor and constructs Meters from the result.

A unit struct represents the fallback case

struct Unknown;

Unknown has no fields and its size is zero. It is still a distinct type that can have methods and trait implementations. Here its From conversion produces Meters(0.0).

Zero-sized does not mean every use of a type has zero runtime cost: methods and trait implementations can do work. This particular conversion simply creates the fallback value.

Mapping an unrecognized unit to zero is this exercise's policy. It loses the distinction between an unknown unit and a real zero-length measurement; another API might preserve that information in a Result or enum.

Constructors and patterns

A tuple-struct constructor can be passed to an iterator's map:

let raw = vec![1.0, 2.0];
let metres: Vec<Meters> = raw.into_iter().map(Meters).collect();
assert_eq!(metres[0].0, 1.0);

It is callable like a function that accepts the fields and builds the struct. A closure such as |value| Meters(value) has the same construction behavior.

A pattern extracts the field again:

let converted = Meters::from(Feet(10.0));
let Meters(value) = converted;
assert!((value - 3.048).abs() < 1e-12);

The starter uses this pattern to return plain f64 values after conversion.

Submissions must compile, pass the cases and pass the Rust Clippy gate.