Rust Data Types

Type Aliases

A type alias declares an alternative name (a synonym) for an existing type using the type keyword (type AliasName = ExistingType;). A type alias does not create a distinct type—it is 100% interchangeable with the underlying type it refers to.

Syntax and Basic Usage

By convention, type aliases use UpperCamelCase:

type Kilometers = i32;
type Point2D = (f64, f64);

fn distance_from_origin(pt: Point2D) -> f64 {
    (pt.0 * pt.0 + pt.1 * pt.1).sqrt()
}

fn main() {
    let local_trip: Kilometers = 15;
    let raw_integer: i32 = 25;

    // Because `Kilometers` is just an alias for `i32`, they can be added directly without casting!
    let total: Kilometers = local_trip + raw_integer;

    let location: Point2D = (3.0, 4.0);
    println!("Total distance: {total} km, hypotenuse: {}", distance_from_origin(location));
}
Total distance: 40 km, hypotenuse: 5

Type Alias vs Newtype Wrapper (struct Meters(i32);)

Because a type alias is a transparent synonym rather than a distinct type, the compiler will not stop you from accidentally passing a Seconds alias to a function expecting a Kilometers alias if both alias i32.

When you want compile-time enforcement that prevents mixing two quantities with the same underlying representation, use a single-field tuple struct (newtype) instead of a type alias:

Feature Type Alias (type Meters = f64;) Newtype Struct (struct Meters(f64);)
Creates a distinct type in rustc? No (Meters and f64 are the exact same type) Yes (Meters and f64 cannot be mixed without explicit .0 unwrap)
Inherits all methods and operators of inner type? Yes (automatically supports +, -, .abs(), etc.) No (only implements traits you explicitly #[derive] or impl)
Can implement new traits on it (Orphan Rule)? No (impl MyTrait for Meters is just impl MyTrait for f64) Yes (Meters is a local type owned by your crate)
Runtime memory overhead 0 bytes 0 bytes
type AliasMeters = u32;
type AliasSeconds = u32;

struct NewtypeMeters(u32);
struct NewtypeSeconds(u32);

fn main() {
    let m: AliasMeters = 100;
    let s: AliasSeconds = 10;

    // Allowed by the compiler (both are `u32`), even though adding meters + seconds is a logic bug!
    let _accidental_sum = m + s;

    let nm = NewtypeMeters(100);
    let ns = NewtypeSeconds(10);
    // `nm + ns` would fail to compile because `NewtypeMeters` and `NewtypeSeconds` are distinct types!
    println!("Newtypes: {} m in {} s", nm.0, ns.0);
}
Newtypes: 100 m in 10 s

Primary Use Cases for Type Aliases

1. Shortening Long, Repetitive Types

Complex nested types—such as boxed closures, thread-safe shared maps, or multi-element tuples—become much easier to read when given a descriptive alias:

use std::collections::HashMap;

type UserId = u64;
type HeadersMap = HashMap<String, Vec<String>>;
type TransformFn = fn(i32) -> i32;

fn apply_all(initial: i32, steps: &[TransformFn]) -> i32 {
    let mut current = initial;
    for step in steps {
        current = step(current);
    }
    current
}

fn main() {
    let mut headers: HeadersMap = HashMap::new();
    headers.insert(String::from("Accept"), vec![String::from("application/json")]);

    let user: UserId = 42;
    let result = apply_all(5, &[|x| x + 1, |x| x * 10]);

    println!("user = {user}, result = {result}, headers = {headers:?}");
}
user = 42, result = 60, headers = {"Accept": ["application/json"]}

2. Partial Generic Specialization (type Result<T> = std::result::Result<T, MyError>;)

A type alias can declare its own generic type parameters <T> while fixing other type parameters to a concrete type. This pattern is used throughout the standard library (such as std::io::Result<T> and std::fmt::Result) so functions within a module do not have to repeat the error type on every signature:

std::io::Result<T>   is defined as   type Result<T> = std::result::Result<T, std::io::Error>;
std::fmt::Result     is defined as   type Result    = std::result::Result<(), std::fmt::Error>;
#[derive(Debug)]
enum ParseError {
    EmptyInput,
    InvalidDigit,
}

// Generic type alias that fixes the `Err` type to `ParseError`:
type ParseResult<T> = std::result::Result<T, ParseError>;

fn parse_single_digit(s: &str) -> ParseResult<u8> {
    let first = s.chars().next().ok_or(ParseError::EmptyInput)?;
    let digit = first.to_digit(10).ok_or(ParseError::InvalidDigit)?;
    Ok(digit as u8)
}

fn main() {
    println!("'7' -> {:?}", parse_single_digit("7"));
    println!("''  -> {:?}", parse_single_digit(""));
    println!("'x' -> {:?}", parse_single_digit("x"));
}
'7' -> Ok(7)
''  -> Err(EmptyInput)
'x' -> Err(InvalidDigit)

3. Referencing Struct and Enum Constructors via Type Aliases

You can also use a type alias to construct struct literals (Alias { ... }) or access enum variants (Alias::Variant):

#[derive(Debug)]
enum ConnectionState {
    Disconnected,
    Connected(u16),
}

type State = ConnectionState;

fn main() {
    let current = State::Connected(443);
    println!("State: {current:?}");
}
State: Connected(443)