Rust struct types — regular, tuple, and unit
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 concept | Rust equivalent | Type safety |
struct { uint8_t r, g, b; } | Regular struct with named fields | Field access by name |
uint8_t arr[3] | Tuple struct with positional fields | Named type identity |
typedef struct {} Empty | Unit struct | Named 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
| Situation | Struct type |
| Each field has distinct meaning | Regular struct |
| Position conveys meaning (RGB, XY) | Tuple struct |
| Distinct type with no data | Unit struct |
| Quick local grouping | Bare 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.