RustArena

Step 5 of 6

Tuple structs, newtypes and unit structs

Sometimes naming every field is overkill. A tuple struct has a name but its fields are only numbered, like a tuple:

struct Rgb(u8, u8, u8);

fn main() {
    let orange = Rgb(255, 140, 0);
    println!("red {}, green {}, blue {}", orange.0, orange.1, orange.2);
}
red 255, green 140, blue 0

Note the definition ends with ; (there are no braces), and you create one like a function call: Rgb(255, 140, 0).

Newtypes: the same number, different meanings

The most useful tuple struct has just one field. Wrapping a plain type to give it a meaning is called the newtype pattern:

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

fn to_meters(length: Feet) -> Meters {
    Meters(length.0 * 0.3048)
}

fn climb(height: Meters) {
    println!("climbing {:.1} m", height.0);
}

fn main() {
    let wall = Feet(100.0);
    climb(to_meters(wall));
    climb(Meters(8848.0));
}
climbing 30.5 m
climbing 8848.0 m

Meters and Feet both hold an f64, but to the compiler they are different types. You can't pass feet where meters are expected:

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

fn climb(height: Meters) -> f64 {
    height.0
}

fn main() {
    let wall = Feet(30.0);
    println!("{}", climb(wall));
}
error[E0308]: mismatched types

This is not a small thing. In 1999 NASA lost the Mars Climate Orbiter because one program produced numbers in pound-force seconds and another read them as newton-seconds. Both were just floating point numbers, so nothing caught it. With newtypes, that mix-up is a compile error. The same idea at work: UserId(u64) and OrderId(u64) so you can never pass an order id to delete_user, or Cents(u32) so nobody adds dollars to cents. A newtype costs nothing at run time: in memory it's exactly the value inside.

The price: a newtype doesn't do arithmetic or printing until you teach it (Meters(3.0) * 2.0 is error[E0369]: cannot multiply `Meters` by `{float}` ). For now, reach inside with .0; the standard library traits module shows how to add operators.

Unit structs

A struct can even have no fields at all: struct Ping;. This unit struct holds no data and takes no memory. It's useful as a marker, or as a type to hang behavior on with traits, later in the course. You create one by writing its name: let p = Ping;.

Your task

Write fn to_fahrenheit(c: Celsius) -> Fahrenheit using the formula F = C × 9 / 5 + 32.

Challenge 2

Time units, as newtypes. Write:

Challenge 3

Money is usually stored as a whole number of cents (floating point can't represent 0.10 exactly). Write a newtype and two functions from scratch:

Tip: convert to f64 only for display, c.0 as f64 / 100.0, and format with {:.2}.

Previous: Printing structs with Debug Next: Putting it together: an inventory