We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Shape Your Data: Structs, Enums, Pattern Matching step 3 of 26
Tuple structs and unit structs: the other two shapes
Convert a list of measurements to metres. Each value comes with a unit
string: "m" means it is already metres, "ft" means feet, and anything
else is unrecognised and normalises to 0.0.
pub fn normalise_distances(values: Vec<f64>, units: Vec<String>) -> Vec<f64>
One foot is exactly 0.3048 metres. If the two vectors have different
lengths, stop at the shorter one. Compare with a tolerance — this is floating
point.
Three shapes of struct, not one
You have met the named-field struct. There are two more, and beginners skip both and then need them constantly.
struct Meters(f64); // tuple struct — fields are .0, .1, ...
struct Unknown; // unit struct — no fields at all
A tuple struct is a struct whose fields are numbered instead of named.
With exactly one field it is called a newtype, and it is one of the most
used patterns in Rust. Meters(f64) and Feet(f64) both wrap the same
machine representation, but they are different types. Hand a Feet to a
function expecting Meters and it does not compile. That is the entire
point: the unit lives in the type, so a unit mix-up becomes a build failure
rather than a Mars orbiter.
A unit struct has no data at all. size_of::<Unknown>() is 0 — it
occupies no memory and costs nothing to pass around. Its whole content is its
identity: it is a name you can attach an impl to. Later in the course you
will use them as typestate markers; here one serves as a stand-in for
“we do not recognise this unit”.
Converting between them: From
Implement From<Feet> for Meters and you get a real conversion:
impl From<Feet> for Meters {
fn from(feet: Feet) -> Self {
Meters(feet.0 * 0.3048)
}
}
feet.0 is how you read a tuple struct’s first field. Writing feet.value
is a compile error, E0609 — the starter ships exactly that so you meet it
once, deliberately.
Implementing From<A> for B gives you B::from(a) and a.into() for
free, because the standard library has a blanket Into implementation that
builds on From. Prefer writing From; you get both directions of the call
site out of it.
The trick worth stealing
A tuple struct’s name also lives in the value namespace — it is an ordinary function of its fields. So this works, and is what an experienced Rust programmer writes:
let metres: Vec<Meters> = raw.into_iter().map(Meters).collect();
Not .map(|x| Meters(x)). Meters is the function fn(f64) -> Meters.
The same is true of tuple-shaped enum variants: .map(Some), .map(Ok),
.map(Event::Write).
And in the other direction, you can pull the value straight back out with a
pattern, in a plain let:
let Meters(m) = converted;
Naming
upper_case_acronyms will reject struct KM(f64). Rust’s convention is
Km, Http, Uuid — acronyms are written like ordinary words in type
names.
Remember the grade is compile + tests + clippy -D warnings.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.