Rust Data Types

Unit Type ()

The unit type, written (), is a built-in primitive type that has exactly one value, also written (). It is technically an empty 0-element tuple (()) and is used whenever a Rust expression, function, or generic type parameter needs a valid type that carries no meaningful payload data.

Dual Role of (): Both a Type and Its Only Value

Just as the 2-tuple value (1, 2) has type (i32, i32), the 0-tuple value () has type ():

fn main() {
    // Left `()` is the type annotation; right `()` is the unit value literal:
    let empty_value: () = ();

    println!("Value: {empty_value:?}");
}
Value: ()

Zero-Sized Memory Representation (0 Bytes)

Because a variable of type () can only ever hold one possible value (()), no bits are needed at runtime to distinguish which value it holds. In Rust, () is a Zero-Sized Type (ZST) whose size is 0 bytes and alignment is 1 byte:

Type          Possible States     size_of::<T>()     align_of::<T>()
──────────    ───────────────     ──────────────     ───────────────
u8            256 (0..=255)       1 byte             1 byte
bool          2 (false, true)     1 byte             1 byte
()            1 ( () )            0 bytes            1 byte
[(); 10_000]  1                   0 bytes            1 byte
use std::mem::{align_of, size_of};

fn main() {
    println!("size_of::<()>()           = {} bytes", size_of::<()>());
    println!("align_of::<()>()          = {} byte", align_of::<()>());
    println!("size_of::<[(); 100_000]>() = {} bytes", size_of::<[(); 100_000]>());
}
size_of::<()>()           = 0 bytes
align_of::<()>()          = 1 byte
size_of::<[(); 100_000]>() = 0 bytes

At compile time, rustc completely erases variables and fields of type () during code generation—reading or passing () emits zero CPU instructions.

Expressions That Evaluate to ()

Because Rust is an expression-oriented language where every block and control-flow construct must have a type, any construct that performs a side effect without producing a value evaluates to ():

Expression / Construct Example Type
Empty block {} ()
Block ending with a ; statement { let x = 5; x + 1; } ()
Assignment / compound assignment x = 10 or x += 1 ()
if without an else branch if ready { start(); } ()
while loop and for loop while cond { ... } / for x in iter { ... } ()
Function with omitted return type fn println_msg() { ... } ()
fn main() {
    let from_empty_block: () = {};
    let mut counter = 0;
    let from_assignment: () = (counter += 1);
    let from_for_loop: () = for _ in 0..3 {};

    println!("{from_empty_block:?}, {from_assignment:?}, {from_for_loop:?} (counter={counter})");
}
(), (), () (counter=1)

Using () in Generic Types (Result<(), E>, HashMap<K, ()>)

Because () is a first-class type implementing Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, and Hash, it plugs into generic data structures with zero memory overhead:

  1. Result<(), E>: Indicates that an operation (such as writing a file or sending a network message) can either succeed with no return payload (Ok(())) or fail with an error (Err(E)).
  2. Zero-cost sets (HashMap<K, ()>): Because () takes 0 bytes in memory, a HashMap<K, ()> stores only keys K without allocating a single byte for values! In fact, the standard library's HashSet<T> is implemented as a thin wrapper around HashMap<T, ()>:
use std::collections::HashMap;
use std::mem::size_of;

fn validate_non_empty(input: &str) -> Result<(), &'static str> {
    if input.is_empty() {
        Err("input string is empty")
    } else {
        Ok(())
    }
}

fn main() {
    println!("validate(\"ok\"): {:?}", validate_non_empty("ok"));
    println!("validate(\"\"):   {:?}", validate_non_empty(""));

    // Storing `()` as the value in a tuple adds 0 bytes of size:
    println!("size_of::<u64>()       = {} bytes", size_of::<u64>());
    println!("size_of::<(u64, ())>() = {} bytes", size_of::<(u64, ())>());

    let mut visited: HashMap<&str, ()> = HashMap::new();
    visited.insert("/home", ());
    println!("Visited /home? {}", visited.contains_key("/home"));
}
validate("ok"): Ok(())
validate(""):   Err("input string is empty")
size_of::<u64>()       = 8 bytes
size_of::<(u64, ())>() = 8 bytes
Visited /home? true