๐Ÿฆ€ Functional Rust

082: Type Aliases

Difficulty: 1 Level: Beginner Give a long or complex type a shorter name with `type` โ€” purely for readability, no new type is created.

The Problem This Solves

`HashMap<String, Vec<(f64, f64)>>` is correct but exhausting to read and repeat. `Result<T, Box<dyn std::error::Error>>` appears in nearly every async function signature. Without aliases, you copy-paste these types everywhere and hope they stay in sync. Type aliases let you name complex types once: `type AppResult<T> = Result<T, String>`. Every place you write `AppResult<User>` is exactly the same as writing `Result<User, String>` โ€” the compiler sees them as identical. The important caveat: `type UserId = u64` does not create a new type. `UserId` and `u64` are the same type โ€” you can pass one where the other is expected without any conversion. If you actually need compile-time separation, use the newtype pattern from example 081. Aliases are documentation for humans; newtypes are enforcement for the compiler.

The Intuition

In Python it's `UserId = int` โ€” a comment more than a constraint. In Java/TypeScript it's `typedef` or `type UserId = number`. In OCaml, `type user_id = int` is also transparent (unless you use a module signature to make it abstract). Rust's `type` alias works the same way: pure documentation, zero enforcement. Think of aliases as meaningful variable names for types. They make code more readable without changing behavior.

How It Works in Rust

// Readable names for common types
type UserId = u64;
type AppResult<T> = Result<T, String>;
type Point = (f64, f64);
type Polygon = Vec<Point>;

// Now function signatures read like English
fn find_user(id: UserId) -> AppResult<User> { ... }
fn area_of(poly: &Polygon) -> f64 { ... }
// Type aliases are transparent โ€” UserId IS u64
let id: UserId = 42;
let raw: u64 = id;   // no conversion needed โ€” they're the same type
// Generic aliases clean up function types
type Validator<T> = fn(&T) -> bool;
type Transform<A, B> = fn(A) -> B;

fn validate_positive(x: &i32) -> bool { *x > 0 }
let v: Validator<i32> = validate_positive;  // reads cleanly
// Box<dyn Fn> aliases reduce repetition
type Predicate<T> = Box<dyn Fn(&T) -> bool>;

fn make_gt(n: i32) -> Predicate<i32> {
 Box::new(move |x| *x > n)
}

What This Unlocks

Key Differences

ConceptOCamlRust
Transparent alias`type user_id = int``type UserId = u64`
Alias vs newtype`type t = int` (transparent) vs `type t = T of int` (opaque)`type T = u64` (transparent) vs `struct T(u64)` (newtype)
Generic alias`type ('a, 'b) result = ...``type Result<T, E> = ...`
InterchangeabilityFully interchangeableFully interchangeable
New type enforcementAbstract module type (opaque)Newtype pattern
// Example 082: Type Aliases
// type keyword in both languages โ€” aliases vs newtypes

// === Approach 1: Simple type aliases ===
type UserId = u64;
type Name = String;
type Age = u32;

struct User {
    id: UserId,
    name: Name,
    age: Age,
}

fn create_user(id: UserId, name: Name, age: Age) -> User {
    User { id, name, age }
}

// === Approach 2: Generic type aliases ===
type ResultWithMsg<T> = Option<(T, String)>;
type Validator<T> = fn(&T) -> bool;
type Transform<A, B> = fn(A) -> B;

fn validate_positive(x: &i32) -> bool { *x > 0 }

// === Approach 3: Complex type aliases for readability ===
type Point = (f64, f64);
type Polygon = Vec<Point>;
type Predicate<T> = Box<dyn Fn(&T) -> bool>;

fn distance(a: Point, b: Point) -> f64 {
    ((b.0 - a.0).powi(2) + (b.1 - a.1).powi(2)).sqrt()
}

fn perimeter(poly: &[Point]) -> f64 {
    if poly.len() < 2 { return 0.0; }
    let mut total = 0.0;
    for i in 0..poly.len() {
        let next = (i + 1) % poly.len();
        total += distance(poly[i], poly[next]);
    }
    total
}

// Type alias for Result with common error
type AppResult<T> = Result<T, String>;

fn parse_age(s: &str) -> AppResult<Age> {
    s.parse::<u32>().map_err(|e| e.to_string())
}

// NOTE: Type aliases do NOT create new types!
// UserId and u64 are interchangeable โ€” no type safety
fn demonstrate_alias_transparency() -> bool {
    let id: UserId = 42;
    let raw: u64 = id;  // No error โ€” same type!
    raw == 42
}

fn filter_with<'a, T>(items: &'a [T], pred: &dyn Fn(&T) -> bool) -> Vec<&'a T> {
    items.iter().filter(|x| pred(x)).collect()
}


#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_create_user() {
        let u = create_user(1, "Bob".into(), 25);
        assert_eq!(u.id, 1);
        assert_eq!(u.name, "Bob");
        assert_eq!(u.age, 25);
    }

    #[test]
    fn test_alias_is_transparent() {
        let id: UserId = 42;
        let raw: u64 = id;
        assert_eq!(raw, 42);
    }

    #[test]
    fn test_validator() {
        let v: Validator<i32> = validate_positive;
        assert!(v(&5));
        assert!(!v(&-1));
        assert!(!v(&0));
    }

    #[test]
    fn test_distance() {
        assert!((distance((0.0, 0.0), (3.0, 4.0)) - 5.0).abs() < 1e-10);
        assert!((distance((1.0, 1.0), (1.0, 1.0))).abs() < 1e-10);
    }

    #[test]
    fn test_perimeter() {
        let square: Polygon = vec![(0.0,0.0), (1.0,0.0), (1.0,1.0), (0.0,1.0)];
        assert!((perimeter(&square) - 4.0).abs() < 1e-10);
    }

    #[test]
    fn test_parse_age() {
        assert_eq!(parse_age("25"), Ok(25));
        assert!(parse_age("abc").is_err());
    }

    #[test]
    fn test_filter_with() {
        let nums = vec![1, 2, 3, 4, 5, 6];
        let evens = filter_with(&nums, &|x| x % 2 == 0);
        assert_eq!(evens, vec![&2, &4, &6]);
    }
}
(* Example 082: Type Aliases *)
(* type keyword in both languages *)

(* Approach 1: Simple type aliases *)
type user_id = int
type name = string
type age = int
type user = { id : user_id; uname : name; uage : age }

let create_user id n a : user = { id; uname = n; uage = a }

(* Approach 2: Parameterized type aliases *)
type 'a result_with_msg = ('a * string) option
type 'a validator = 'a -> bool
type ('a, 'b) transform = 'a -> 'b

let validate_positive : int validator = fun x -> x > 0
let int_to_string : (int, string) transform = string_of_int

(* Approach 3: Complex type aliases for readability *)
type point = float * float
type line = point * point
type polygon = point list

let distance ((x1, y1) : point) ((x2, y2) : point) : float =
  sqrt ((x2 -. x1) ** 2.0 +. (y2 -. y1) ** 2.0)

let perimeter (poly : polygon) : float =
  match poly with
  | [] | [_] -> 0.0
  | first :: _ ->
    let rec aux acc = function
      | [] -> acc
      | [last] -> acc +. distance last first
      | a :: (b :: _ as rest) -> aux (acc +. distance a b) rest
    in
    aux 0.0 poly

type 'a predicate = 'a -> bool
type 'a comparator = 'a -> 'a -> int

let filter_with (pred : 'a predicate) lst = List.filter pred lst
let sort_with (cmp : 'a comparator) lst = List.sort cmp lst

(* Tests *)
let () =
  let u = create_user 1 "Alice" 30 in
  assert (u.id = 1);
  assert (u.uname = "Alice");

  assert (validate_positive 5 = true);
  assert (validate_positive (-1) = false);
  assert (int_to_string 42 = "42");

  let p1 : point = (0.0, 0.0) in
  let p2 : point = (3.0, 4.0) in
  assert (abs_float (distance p1 p2 -. 5.0) < 0.001);

  let square : polygon = [(0.0, 0.0); (1.0, 0.0); (1.0, 1.0); (0.0, 1.0)] in
  assert (abs_float (perimeter square -. 4.0) < 0.001);

  let evens = filter_with (fun x -> x mod 2 = 0) [1;2;3;4;5;6] in
  assert (evens = [2;4;6]);

  Printf.printf "โœ“ All tests passed\n"

๐Ÿ“Š Detailed Comparison

Comparison: Type Aliases

Simple Aliases

OCaml:

๐Ÿช Show OCaml equivalent
type user_id = int
type name = string
type user = { id : user_id; uname : name }

Rust:

type UserId = u64;
type Name = String;
struct User { id: UserId, name: Name }

Generic Aliases

OCaml:

๐Ÿช Show OCaml equivalent
type 'a validator = 'a -> bool
type ('a, 'b) transform = 'a -> 'b

Rust:

type Validator<T> = fn(&T) -> bool;
type Transform<A, B> = fn(A) -> B;

Complex Type Shorthand

OCaml:

๐Ÿช Show OCaml equivalent
type point = float * float
type polygon = point list
type 'a predicate = 'a -> bool

Rust:

type Point = (f64, f64);
type Polygon = Vec<Point>;
type Predicate<T> = Box<dyn Fn(&T) -> bool>;

Aliases are Transparent (Both Languages)

OCaml:

๐Ÿช Show OCaml equivalent
type user_id = int
let x : user_id = 42
let y : int = x  (* OK โ€” same type *)

Rust:

type UserId = u64;
let x: UserId = 42;
let y: u64 = x;  // OK โ€” same type