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
- Readable domain types: `type Timestamp = u64`, `type Matrix = Vec<Vec<f64>>` โ the type tells you what the value means, not just its structure.
- Consistent error types: `type AppResult<T> = Result<T, AppError>` used across a whole crate ensures all functions use the same error type without repetition.
- Documentation in signatures: `fn compute(polygon: &Polygon) -> Area` reads like a specification; `fn compute(polygon: &Vec<(f64, f64)>) -> f64` does not.
Key Differences
| Concept | OCaml | Rust |
|---|---|---|
| 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> = ...` |
| Interchangeability | Fully interchangeable | Fully interchangeable |
| New type enforcement | Abstract 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