Centameta
← Back to Blog

Rust struct types — regular, tuple, and unit

July 02, 2026

Rust gives you three ways to define a struct. Each exists because sometimes naming a field is helpful, sometimes position is enough, and sometimes you only need a named type with no data.

struct Regular {
    red: u8,
    green: u8,
    blue: u8,
}

struct Tuple(u8, u8, u8);
struct Unit;

Regular struct — named fields

The standard form. Every field has a name and a type.

struct ColorRegularStruct {
    red: u8,
    green: u8,
    blue: u8,
}

let green = ColorRegularStruct {
    red: 0,
    green: 255,
    blue: 0,
};

println!("{}", green.red);

Named fields make code self-documenting. When you see green.red, intent is explicit.

Tuple struct — positional fields

Fields have positions instead of names. Access them with .0, .1, .2.

struct ColorTupleStruct(u8, u8, u8);

let green = ColorTupleStruct(0, 255, 0);
println!("{}", green.0);

Unlike a bare tuple (u8, u8, u8), a tuple struct is its own type. You cannot accidentally interchange it with another tuple struct of the same field shapes.

struct Rgb(u8, u8, u8);
struct Hsv(f32, f32, f32);

let c = Rgb(255, 0, 0);
let d = Hsv(0.0, 1.0, 0.5);
// c = d; // type mismatch

The C embedded analogy

In C, named structs and positional arrays are common:

struct sensor {
    uint32_t raw;
    uint8_t channel;
};

uint8_t rgb[3] = {0, 255, 0};
rgb[0];

Rust tuple structs keep positional access while adding type identity, which prevents accidental mixing of unrelated values that share the same primitive layout.

C conceptRust equivalentType safety
struct { uint8_t r, g, b; }Regular struct with named fieldsField access by name
uint8_t arr[3]Tuple struct with positional fieldsNamed type identity
typedef struct {} EmptyUnit structNamed zero-size type

Unit struct — the zero-size marker

struct UnitStruct;

let unit = UnitStruct;
println!("{:?}s are fun!", unit);

Unit structs are useful for type-state and marker-style APIs with zero runtime storage.

trait Register {
    fn address(&self) -> u32;
}

struct AdcConfig;
impl Register for AdcConfig {
    fn address(&self) -> u32 { 0x4001_2000 }
}

struct DmaConfig;
impl Register for DmaConfig {
    fn address(&self) -> u32 { 0x4002_0000 }
}

Which one to use

SituationStruct type
Each field has distinct meaningRegular struct
Position conveys meaning (RGB, XY)Tuple struct
Distinct type with no dataUnit struct
Quick local groupingBare tuple

The core lesson

These forms represent different levels of type identity. Pick the least ceremony that still makes incorrect code look incorrect to the compiler.