---
title: "10.4 Tuple structs and unit structs"
description: "Tuple structs give a tuple a name and its own type, the newtype pattern wraps a value to make it distinct, and unit structs carry no data at all."
url: "https://learnrust.net/chapter-10/tuple-structs-and-unit-structs/"
last_updated: "2026-06-13"
---

# 10.4 Tuple structs and unit structs

Named-field structs are the workhorses, but Rust has two leaner variants for cases where named fields are more ceremony than the data deserves. Both are still program-defined types; both still create a brand-new type the compiler will keep separate from everything else.

## Tuple structs

A **tuple struct** is a struct whose fields have positions instead of names. You give the type a name, then list the field types in parentheses:

```rust
struct Rgb(u8, u8, u8);

fn main() {
    let orange = Rgb(206, 66, 43);
    println!("R={} G={} B={}", orange.0, orange.1, orange.2);
}
```

```
R=206 G=66 B=43
```

It looks like a tuple (lesson [4.11](https://learnrust.net/chapter-4/tuples-and-the-unit-type/)) and you reach into it the same way, with `.0`, `.1`, `.2`. The difference is the name in front. `Rgb(206, 66, 43)` is not a plain `(u8, u8, u8)` tuple; it is a value of the distinct type `Rgb`. A function that takes an `Rgb` will refuse a bare tuple, and vice versa.

Use a tuple struct when the fields' meaning is obvious from order and context, so names would just be noise. A color as `(red, green, blue)` is the classic case: everyone agrees on the order, and `Rgb(206, 66, 43)` reads better than a named-field struct with three one-letter fields. When the order *isn't* obvious, prefer named fields. Nobody should have to remember whether `.2` was the balance or the year.

## The newtype pattern

The most valuable use of a single-field tuple struct has its own name: the **newtype pattern**. You wrap an existing type to give it a fresh identity the compiler enforces.

Consider a program that handles both meters and feet, both stored as `f64`. Nothing stops you from adding a meters value to a feet value; they're both `f64`, so the compiler is happy and your bridge is wrong. Wrap each in its own type and the mistake becomes a compile error:

```rust
struct Meters(f64);
struct Feet(f64);

fn describe(distance: Meters) {
    println!("{} meters", distance.0);
}

fn main() {
    let height = Meters(8848.0);
    describe(height);
    // describe(Feet(29029.0)); // refused: Feet is not Meters
}
```

```
8848 meters
```

`Meters` and `Feet` are both "an `f64` underneath," but they are different *types*, so `describe(Feet(29029.0))` won't compile. This is the lesson [10.1](https://learnrust.net/chapter-10/introduction-to-program-defined-types/) idea ("`EmailAddress` beats `String` when precision earns its keep") made mechanical. The wrapper costs nothing at runtime; it's the same `f64` in memory, with a type-level label that catches mix-ups for free.

> **Key insight**
>
> A newtype turns a comment into a compiler check. Instead of writing `// this f64 is in meters` and hoping every caller read it, you make `Meters` a type, and "this is meters, not feet" becomes a rule the compiler enforces at every call site. The same trick wraps a `String` into a `UserId`, a `u64` into a `Timestamp`, and so on, whenever you want to stop two look-alike values from being mistaken for each other.

## Unit structs

The leanest variant has no fields at all. A **unit struct** is declared with just a name and a semicolon:

```rust
struct AlwaysReady;

fn main() {
    let _marker = AlwaysReady;
}
```

It's called a *unit* struct because it holds no data, the way the unit type `()` from lesson [1.11](https://learnrust.net/chapter-1/expressions-and-statements/) holds no data. At first this seems pointless: a type with nothing in it stores nothing, so why create one? The answer is that a type can carry *behavior* and *meaning* without carrying data. You'll meet the real use in chapter 16, where you can attach an implementation of some shared behavior (a trait) to a unit struct, so the type itself, with no fields, becomes a thing you can pass around and dispatch on. For now, just recognize the syntax: a bare name and a semicolon is a valid struct.

> **For advanced readers**
>
> Unit structs come into their own as zero-sized markers: a `Celsius` and a `Fahrenheit` unit struct used as type parameters, or a state-machine state that needs no data. Because they occupy no memory, the compiler can represent a whole collection of them for free. Chapter 15's generics and chapter 16's traits are where this stops being a curiosity.

## Three flavors, one idea

All three are structs; they differ only in how the fields are addressed:

- **Named-field struct** (`struct Point { x: f64, y: f64 }`): fields by name, the default choice.
- **Tuple struct** (`struct Rgb(u8, u8, u8)`): fields by position; good when order is self-evident, and the home of the newtype pattern.
- **Unit struct** (`struct AlwaysReady;`): no fields; a type that carries meaning or behavior rather than data.

Reach for named fields unless you have a specific reason not to. The other two are tools for specific jobs, not everyday alternatives.

## Quiz time

**Question #1**

Define a tuple struct `Point3d` holding three `f64` values, create the point `(1.0, 2.0, 3.0)`, and print its middle coordinate.

<details class="solution">
<summary>Show solution</summary>

```rust
struct Point3d(f64, f64, f64);

fn main() {
    let p = Point3d(1.0, 2.0, 3.0);
    println!("{}", p.1);
}
```

Prints `2`. Fields are positional, so the middle one is `.1`.

</details>

**Question #2**

Why won't this compile, and what real bug does the refusal prevent?

```rust
struct Meters(f64);
struct Feet(f64);

fn main() {
    let total = Meters(100.0).0 + Feet(50.0).0;
    println!("{total}");
}
```

<details class="solution">
<summary>Show solution</summary>

Trick question: that line actually *does* compile, because `.0` unwraps each to a bare `f64` and adding two `f64`s is fine, giving `150`. The newtype only protects you while values stay wrapped. The bug the pattern prevents is passing a `Feet` where a function expects `Meters`: `describe(Feet(50.0))` is refused. Once you manually unwrap with `.0`, you've taken responsibility, the same way `as` did in lesson [4.10](https://learnrust.net/chapter-4/numeric-conversions/).

</details>

**Question #3**

What's the difference between `struct Empty;` and `struct Empty {}`? When would you write a unit struct at all?

<details class="solution">
<summary>Show solution</summary>

Both are fieldless types; `struct Empty;` is the unit-struct form (no braces, semicolon) and is the conventional way to write "no fields." You'd use one when you need a type that carries behavior or meaning but no data, which becomes useful with traits in chapter 16. Until then it's enough to recognize the syntax.

</details>

Structs can now hold data in three shapes. The next lesson attaches *behavior* to them: methods, the `impl` block, and the three forms of `self` that turn chapters 8 and 9 into everyday syntax.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
