ExamplesBy LevelBy TopicLearning Paths
082 Intermediate

082 — Type Aliases

Functional Programming

Tutorial

The Problem

Use type aliases to give shorter, more descriptive names to complex type expressions. Define Point, Name, AppResult<T>, Predicate<T>, and Transform<T> — reducing repetition in signatures and improving readability — and compare with OCaml's equivalent type declarations.

🎯 Learning Outcomes

  • • Write type aliases with type Name = ... in Rust
  • • Create generic aliases like type AppResult<T> = Result<T, AppError>
  • • Understand that aliases are transparent: the compiler sees through them
  • • Distinguish type aliases (transparent) from newtypes (opaque wrapper structs)
  • • Use type Predicate<T> = Box<dyn Fn(&T) -> bool> for complex closure types
  • • Map Rust aliases to OCaml type 'a result_t = ('a, error) result
  • Code Example

    #![allow(clippy::all)]
    // 082: Type Aliases
    // Shortening complex types with type aliases
    
    // Approach 1: Simple aliases
    type Point = (f64, f64);
    type Name = String;
    
    fn distance(p1: Point, p2: Point) -> f64 {
        ((p2.0 - p1.0).powi(2) + (p2.1 - p1.1).powi(2)).sqrt()
    }
    
    // Approach 2: Result alias (common pattern)
    #[derive(Debug, PartialEq)]
    enum AppError {
        ParseError(String),
        DivByZero,
    }
    
    type AppResult<T> = Result<T, AppError>;
    
    fn parse_int(s: &str) -> AppResult<i32> {
        s.parse()
            .map_err(|_| AppError::ParseError(format!("Not a number: {}", s)))
    }
    
    fn safe_div(a: i32, b: i32) -> AppResult<i32> {
        if b == 0 {
            Err(AppError::DivByZero)
        } else {
            Ok(a / b)
        }
    }
    
    // Approach 3: Complex type aliases
    type Predicate<T> = Box<dyn Fn(&T) -> bool>;
    type Transform<T> = Box<dyn Fn(T) -> T>;
    
    fn filter_map_custom<T: Clone, U>(
        items: &[T],
        pred: &dyn Fn(&T) -> bool,
        f: &dyn Fn(T) -> U,
    ) -> Vec<U> {
        items
            .iter()
            .filter(|x| pred(x))
            .map(|x| f(x.clone()))
            .collect()
    }
    
    // io::Result pattern
    type IoResult<T> = std::io::Result<T>;
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_distance() {
            assert!((distance((0.0, 0.0), (3.0, 4.0)) - 5.0).abs() < 0.001);
        }
    
        #[test]
        fn test_parse_int() {
            assert_eq!(parse_int("42"), Ok(42));
            assert!(parse_int("abc").is_err());
        }
    
        #[test]
        fn test_safe_div() {
            assert_eq!(safe_div(10, 3), Ok(3));
            assert_eq!(safe_div(10, 0), Err(AppError::DivByZero));
        }
    
        #[test]
        fn test_filter_map() {
            let result = filter_map_custom(&[1, 2, 3, 4, 5, 6], &|x: &i32| x % 2 == 0, &|x: i32| x * 2);
            assert_eq!(result, vec![4, 8, 12]);
        }
    }

    Key Differences

    AspectRustOCaml
    Syntaxtype Name = Typetype name = type
    Generictype Foo<T> = ...type 'a foo = ...
    TransparencyFully transparentFully transparent
    vs newtypestruct Meters(f64) is opaquetype meters = Meters of float is opaque
    Common useio::Result<T>, Predicate<T>'a option, custom result aliases
    Closure aliasBox<dyn Fn(...)> neededFirst-class function type 'a -> 'b

    The key distinction: type aliases are purely cosmetic and transparent; newtypes (tuple structs in Rust, single-constructor variants in OCaml) create genuinely new types with type-checking separation. Use an alias when you want shorter notation; use a newtype when you want compile-time separation.

    OCaml Approach

    OCaml's type point = float * float and type 'a predicate = 'a -> bool serve the same purpose. Parameterised aliases use the 'a syntax: type 'a result_t = ('a, error) result. OCaml's type system treats aliases as identical to their expansion — no subtyping, no coercion needed. The notation is slightly different ('a result_t vs AppResult<T>) but the semantics are identical.

    Full Source

    #![allow(clippy::all)]
    // 082: Type Aliases
    // Shortening complex types with type aliases
    
    // Approach 1: Simple aliases
    type Point = (f64, f64);
    type Name = String;
    
    fn distance(p1: Point, p2: Point) -> f64 {
        ((p2.0 - p1.0).powi(2) + (p2.1 - p1.1).powi(2)).sqrt()
    }
    
    // Approach 2: Result alias (common pattern)
    #[derive(Debug, PartialEq)]
    enum AppError {
        ParseError(String),
        DivByZero,
    }
    
    type AppResult<T> = Result<T, AppError>;
    
    fn parse_int(s: &str) -> AppResult<i32> {
        s.parse()
            .map_err(|_| AppError::ParseError(format!("Not a number: {}", s)))
    }
    
    fn safe_div(a: i32, b: i32) -> AppResult<i32> {
        if b == 0 {
            Err(AppError::DivByZero)
        } else {
            Ok(a / b)
        }
    }
    
    // Approach 3: Complex type aliases
    type Predicate<T> = Box<dyn Fn(&T) -> bool>;
    type Transform<T> = Box<dyn Fn(T) -> T>;
    
    fn filter_map_custom<T: Clone, U>(
        items: &[T],
        pred: &dyn Fn(&T) -> bool,
        f: &dyn Fn(T) -> U,
    ) -> Vec<U> {
        items
            .iter()
            .filter(|x| pred(x))
            .map(|x| f(x.clone()))
            .collect()
    }
    
    // io::Result pattern
    type IoResult<T> = std::io::Result<T>;
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_distance() {
            assert!((distance((0.0, 0.0), (3.0, 4.0)) - 5.0).abs() < 0.001);
        }
    
        #[test]
        fn test_parse_int() {
            assert_eq!(parse_int("42"), Ok(42));
            assert!(parse_int("abc").is_err());
        }
    
        #[test]
        fn test_safe_div() {
            assert_eq!(safe_div(10, 3), Ok(3));
            assert_eq!(safe_div(10, 0), Err(AppError::DivByZero));
        }
    
        #[test]
        fn test_filter_map() {
            let result = filter_map_custom(&[1, 2, 3, 4, 5, 6], &|x: &i32| x % 2 == 0, &|x: i32| x * 2);
            assert_eq!(result, vec![4, 8, 12]);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_distance() {
            assert!((distance((0.0, 0.0), (3.0, 4.0)) - 5.0).abs() < 0.001);
        }
    
        #[test]
        fn test_parse_int() {
            assert_eq!(parse_int("42"), Ok(42));
            assert!(parse_int("abc").is_err());
        }
    
        #[test]
        fn test_safe_div() {
            assert_eq!(safe_div(10, 3), Ok(3));
            assert_eq!(safe_div(10, 0), Err(AppError::DivByZero));
        }
    
        #[test]
        fn test_filter_map() {
            let result = filter_map_custom(&[1, 2, 3, 4, 5, 6], &|x: &i32| x % 2 == 0, &|x: i32| x * 2);
            assert_eq!(result, vec![4, 8, 12]);
        }
    }

    Deep Comparison

    Core Insight

    Type aliases give shorter names to complex types. They're transparent — the compiler treats them as identical to the original. Useful for Result types, complex generics, and documentation.

    OCaml Approach

  • type 'a my_result = ('a, error) result
  • • Aliases are fully transparent
  • • Can also use type t = int for simple aliases
  • Rust Approach

  • type Result<T> = std::result::Result<T, MyError>;
  • • Common in library APIs (io::Result<T>)
  • • No new type created — just a name
  • Comparison Table

    FeatureOCamlRust
    Syntaxtype alias = originaltype Alias = Original;
    TransparentYesYes
    Generictype 'a t = ...type T<A> = ...
    New type?NoNo

    Exercises

  • Define type Matrix = Vec<Vec<f64>> and write a transpose(m: &Matrix) -> Matrix function using it.
  • Create type Parser<T> = Box<dyn Fn(&str) -> Option<(T, &str)>> and implement a digit parser and a letter parser.
  • Write type ResultVec<T, E> = Vec<Result<T, E>> and a function partition_results that splits it into (Vec<T>, Vec<E>).
  • Demonstrate the transparency: write a function that accepts AppResult<i32> and call it with Result<i32, AppError> directly.
  • In OCaml, define type ('a, 'b) either = Left of 'a | Right of 'b and write a partition_eithers function. Compare this design with Rust's Result.
  • Open Source Repos