ExamplesBy LevelBy TopicLearning Paths
876 Intermediate

876-type-aliases — Type Aliases

Functional Programming

Tutorial

The Problem

Long or repetitive type signatures hurt readability. HashMap<String, Vec<(usize, f64)>> is tedious to write repeatedly, and Box<dyn Fn(&str) -> Result<i32, ParseError>> is worse. Type aliases provide a way to name complex types for clarity and consistency. The key tradeoff: unlike newtypes, aliases are transparent to the type checker — UserId and u64 are the same type. This makes aliases appropriate for documentation and convenience (not safety). Both Rust and OCaml use the type keyword for aliases, though OCaml's aliases can also be parameterized with additional constraints via module types.

🎯 Learning Outcomes

  • • Define simple and parameterized type aliases using the type keyword
  • • Understand that type aliases are transparent — no new type is created
  • • Use aliases to clarify complex generic signatures like Result<T, ParseError>
  • • Contrast type aliases with newtypes for documentation vs safety purposes
  • • Recognize the common alias pattern type Result<T> = std::result::Result<T, MyError>
  • Code Example

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

    Key Differences

  • Transparency: Both languages make aliases completely transparent — no implicit conversion, no runtime cost, and no new type is created.
  • Safety: Type aliases provide zero type safety compared to newtypes/abstract types; they are documentation only.
  • Generic syntax: Rust uses type Foo<T> = Bar<T>; OCaml uses type 'a foo = 'a bar.
  • Module type aliases: OCaml can alias module types (module type S = OtherModule.S); Rust has no direct equivalent at the module level.
  • OCaml Approach

    OCaml uses the same type keyword: type user_id = int, type point = float * float. Parameterized aliases: type 'a validator = 'a -> bool, type ('a, 'b) transform = 'a -> 'b. Like Rust, OCaml aliases are fully transparent — user_id and int unify without coercion. OCaml's module system allows type t = MyModule.t to import a type from another module, which is a common idiom for adapters and wrappers.

    Full Source

    #![allow(clippy::all)]
    // 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]);
        }
    }
    ✓ Tests Rust test suite
    #[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]);
        }
    }

    Deep Comparison

    Comparison: Type Aliases

    Simple Aliases

    OCaml:

    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:

    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:

    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:

    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
    

    Exercises

  • Define type Matrix = Vec<Vec<f64>> and write add_matrices, scale_matrix, and transpose functions using this alias.
  • Create type Parser<T> = Box<dyn Fn(&str) -> Result<T, String>> and implement a combinator and_then_parser that chains two parsers.
  • Refactor any earlier example that uses a long type signature to use an alias, and explain whether a newtype would be more appropriate.
  • Open Source Repos