ExamplesBy LevelBy TopicLearning Paths
1184 Intermediate

Array.make and Array.make_matrix — Multi-dimensional Arrays

Functional Programming

Tutorial

The Problem

Create and manipulate one-dimensional and two-dimensional mutable arrays. Array.make n v allocates a flat array of n elements all initialized to v; Array.make_matrix rows cols v allocates a 2D grid initialized uniformly. This example covers the primary mutable collection primitives in OCaml and their idiomatic Rust equivalents, including how to read and write individual cells and how to iterate over rows and columns with higher-order functions.

🎯 Learning Outcomes

  • • How OCaml's Array.make 5 0 maps to Rust's vec![0; 5] — both create a flat initialized array but express it through different syntax
  • • How OCaml's Array.make_matrix 3 4 0.0 maps to Rust's vec![vec![0.0f64; 4]; 3] — a Vec<Vec<T>> is the idiomatic 2D structure in safe Rust
  • • How mutable cell access differs: OCaml uses matrix.(row).(col) <- value; Rust uses matrix[row][col] = value after binding with let mut
  • • Why Rust requires explicit mut to allow any mutation, while OCaml arrays are always mutable by construction
  • • How Array.iter and Array.iteri map to .iter() and .iter().enumerate() in Rust, and how row-by-row iteration over a 2D structure looks in both languages
  • Code Example

    // 1D: vec![value; n] mirrors Array.make n value
    let zeros: Vec<i32> = vec![0; 5];
    
    // 2D: Vec<Vec<T>> mirrors Array.make_matrix rows cols value
    let mut matrix: Vec<Vec<f64>> = vec![vec![0.0; 4]; 3];
    matrix[1][2] = 42.0;
    
    for row in &matrix {
        for x in row {
            print!("{:.0} ", x);
        }
        println!();
    }

    Key Differences

  • Allocation syntax: OCaml: Array.make n v (function call, count then value); Rust: vec![v; n] (macro, value then count) — the argument order is reversed, which is a common source of off-by-one confusion when translating.
  • Mutability declaration: OCaml arrays are always mutable; Rust variables must be declared let mut before the first write, and the compiler rejects any attempted mutation of a non-mut binding at compile time.
  • Cell update syntax: OCaml uses the .<- operator matrix.(r).(c) <- v; Rust uses index notation with assignment matrix[r][c] = v after the binding is declared mut.
  • Memory layout: Both languages represent a 2D array as an array of pointers to independent row arrays ('a array array / Vec<Vec<T>>), so rows are not contiguous in memory. For performance-critical numeric work, a flat Vec<T> with manual index arithmetic (row * cols + col) is preferred in both languages.
  • OCaml Approach

    OCaml provides Array.make : int -> 'a -> 'a array to allocate a 1D array filled with a single initial value. For 2D grids, Array.make_matrix : int -> int -> 'a -> 'a array array creates an array of arrays — each row is an independent array, so rows do not share storage. Cell mutation uses the dedicated update operator: matrix.(1).(2) <- 42.0 sets row 1, column 2 to 42.0. All OCaml arrays are mutable by default; no mutable annotation is needed. Iteration uses Array.iter (fun row -> ...) matrix for rows, nested with another Array.iter for cells, mapping naturally to the Rust nested iterator pattern.

    Full Source

    #![allow(dead_code)]
    //! Array.make and Array.make_matrix — Multi-Dimensional Arrays
    //! See example.ml for OCaml reference
    //!
    //! OCaml's `Array.make n x` creates a 1D array of `n` copies of `x`.
    //! OCaml's `Array.make_matrix rows cols x` creates a 2D array (array of arrays).
    //! Rust uses `vec![x; n]` and `Vec<Vec<T>>` respectively.
    
    /// Create a 1D vector of `n` copies of `value`.
    /// Mirrors OCaml: `Array.make n value`
    pub fn make<T: Clone>(n: usize, value: T) -> Vec<T> {
        vec![value; n]
    }
    
    /// Create a 2D matrix of `rows` × `cols` with every cell set to `value`.
    /// Mirrors OCaml: `Array.make_matrix rows cols value`
    /// Each row is an independent Vec — mutating one row does not affect others.
    pub fn make_matrix<T: Clone>(rows: usize, cols: usize, value: T) -> Vec<Vec<T>> {
        vec![vec![value; cols]; rows]
    }
    
    /// Set a cell in a 2D matrix. Returns `None` if coordinates are out of bounds.
    /// Mirrors OCaml: `matrix.(row).(col) <- new_value`
    pub fn matrix_set<T: Clone>(matrix: &mut [Vec<T>], row: usize, col: usize, value: T) -> bool {
        if let Some(r) = matrix.get_mut(row) {
            if let Some(cell) = r.get_mut(col) {
                *cell = value;
                return true;
            }
        }
        false
    }
    
    /// Get a cell from a 2D matrix. Returns `None` if out of bounds.
    pub fn matrix_get<T>(matrix: &[Vec<T>], row: usize, col: usize) -> Option<&T> {
        matrix.get(row).and_then(|r| r.get(col))
    }
    
    /// Create an identity matrix (1s on diagonal, 0s elsewhere).
    pub fn identity_matrix(n: usize) -> Vec<Vec<f64>> {
        let mut m = make_matrix(n, n, 0.0_f64);
        for (i, row) in m.iter_mut().enumerate() {
            row[i] = 1.0;
        }
        m
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_make_zeros() {
            assert_eq!(make(5, 0_i32), vec![0, 0, 0, 0, 0]);
        }
    
        #[test]
        fn test_make_single_element() {
            assert_eq!(make(1, 42_i32), vec![42]);
        }
    
        #[test]
        fn test_make_empty() {
            let v: Vec<i32> = make(0, 0);
            assert!(v.is_empty());
        }
    
        #[test]
        fn test_make_matrix_dimensions() {
            let m = make_matrix(3, 4, 0.0_f64);
            assert_eq!(m.len(), 3);
            for row in &m {
                assert_eq!(row.len(), 4);
            }
        }
    
        #[test]
        fn test_make_matrix_set_and_get() {
            let mut m = make_matrix(3, 4, 0.0_f64);
            assert!(matrix_set(&mut m, 1, 2, 42.0));
            assert_eq!(matrix_get(&m, 1, 2), Some(&42.0));
            // Other cells still zero.
            assert_eq!(matrix_get(&m, 0, 0), Some(&0.0));
        }
    
        #[test]
        fn test_matrix_set_out_of_bounds() {
            let mut m = make_matrix(3, 4, 0.0_f64);
            assert!(!matrix_set(&mut m, 5, 0, 99.0));
            assert!(!matrix_set(&mut m, 0, 10, 99.0));
        }
    
        #[test]
        fn test_rows_are_independent() {
            // Modifying one row must not affect other rows (independent Vecs).
            let mut m = make_matrix(3, 3, 0_i32);
            m[0][0] = 99;
            assert_eq!(m[1][0], 0);
            assert_eq!(m[2][0], 0);
        }
    
        #[test]
        fn test_identity_matrix() {
            let id = identity_matrix(3);
            for i in 0..3 {
                for j in 0..3 {
                    let expected = if i == j { 1.0 } else { 0.0 };
                    assert_eq!(id[i][j], expected);
                }
            }
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_make_zeros() {
            assert_eq!(make(5, 0_i32), vec![0, 0, 0, 0, 0]);
        }
    
        #[test]
        fn test_make_single_element() {
            assert_eq!(make(1, 42_i32), vec![42]);
        }
    
        #[test]
        fn test_make_empty() {
            let v: Vec<i32> = make(0, 0);
            assert!(v.is_empty());
        }
    
        #[test]
        fn test_make_matrix_dimensions() {
            let m = make_matrix(3, 4, 0.0_f64);
            assert_eq!(m.len(), 3);
            for row in &m {
                assert_eq!(row.len(), 4);
            }
        }
    
        #[test]
        fn test_make_matrix_set_and_get() {
            let mut m = make_matrix(3, 4, 0.0_f64);
            assert!(matrix_set(&mut m, 1, 2, 42.0));
            assert_eq!(matrix_get(&m, 1, 2), Some(&42.0));
            // Other cells still zero.
            assert_eq!(matrix_get(&m, 0, 0), Some(&0.0));
        }
    
        #[test]
        fn test_matrix_set_out_of_bounds() {
            let mut m = make_matrix(3, 4, 0.0_f64);
            assert!(!matrix_set(&mut m, 5, 0, 99.0));
            assert!(!matrix_set(&mut m, 0, 10, 99.0));
        }
    
        #[test]
        fn test_rows_are_independent() {
            // Modifying one row must not affect other rows (independent Vecs).
            let mut m = make_matrix(3, 3, 0_i32);
            m[0][0] = 99;
            assert_eq!(m[1][0], 0);
            assert_eq!(m[2][0], 0);
        }
    
        #[test]
        fn test_identity_matrix() {
            let id = identity_matrix(3);
            for i in 0..3 {
                for j in 0..3 {
                    let expected = if i == j { 1.0 } else { 0.0 };
                    assert_eq!(id[i][j], expected);
                }
            }
        }
    }

    Deep Comparison

    OCaml vs Rust: Array.make and Array.make_matrix — Multi-Dimensional Arrays

    Side-by-Side Code

    OCaml

    let zeros = Array.make 5 0
    let matrix = Array.make_matrix 3 4 0.0
    let () = matrix.(1).(2) <- 42.0
    let () =
      Array.iter (fun row ->
        Array.iter (fun x -> Printf.printf "%.0f " x) row;
        print_newline ()
      ) matrix
    

    Rust (idiomatic)

    // 1D: vec![value; n] mirrors Array.make n value
    let zeros: Vec<i32> = vec![0; 5];
    
    // 2D: Vec<Vec<T>> mirrors Array.make_matrix rows cols value
    let mut matrix: Vec<Vec<f64>> = vec![vec![0.0; 4]; 3];
    matrix[1][2] = 42.0;
    
    for row in &matrix {
        for x in row {
            print!("{:.0} ", x);
        }
        println!();
    }
    

    Rust (functional — helper functions)

    pub fn make<T: Clone>(n: usize, value: T) -> Vec<T> {
        vec![value; n]
    }
    
    pub fn make_matrix<T: Clone>(rows: usize, cols: usize, value: T) -> Vec<Vec<T>> {
        vec![vec![value; cols]; rows]
    }
    

    Type Signatures

    ConceptOCamlRust
    1D arrayArray.make : int -> 'a -> 'a arrayvec![value; n] or fn make<T: Clone>(n: usize, v: T) -> Vec<T>
    2D matrixArray.make_matrix : int -> int -> 'a -> 'a array arrayvec![vec![value; cols]; rows]Vec<Vec<T>>
    Index accessarr.(i)arr[i]
    Mutationarr.(i) <- v (in-place)arr[i] = v (in-place)
    Bounds checkruntime exceptionruntime panic (in debug), or use .get_mut() for Option

    Key Insights

  • Independent rows: OCaml's Array.make_matrix creates independent row arrays — mutating matrix.(1).(2) does not affect row 0 or row 2. Rust's vec![vec![value; cols]; rows] has the same semantics because each inner Vec is an independent allocation.
  • Mutation model: Both languages support in-place mutation (matrix.(1).(2) <- 42.0 in OCaml, matrix[1][2] = 42.0 in Rust). OCaml arrays are mutable by default; Rust requires mut.
  • Bounds checking: OCaml raises Invalid_argument on out-of-bounds access at runtime; Rust panics in debug mode. Both languages offer safe alternatives (Array.get in OCaml, slice.get() in Rust) that return Option.
  • Generic over type: Both functions are generic — OCaml uses parametric polymorphism 'a, Rust uses T: Clone. The Clone bound is needed because vec![value; n] clones the value n - 1 times.
  • Functional style: OCaml encourages immutable data structures; arrays are the exception (the imperative escape hatch). Rust similarly prefers iterators but Vec is the standard mutable sequence.
  • When to Use Each Style

    **Use vec![value; n] / vec![vec![...]; rows] when:** creating fixed-size arrays in production code — it's the idiomatic, zero-boilerplate Rust approach. **Use helper functions make / make_matrix when:** you want a direct parallel to OCaml's Array.make API for educational purposes or when building a uniform interface over matrix creation.

    Exercises

  • Implement make_identity(n: usize) -> Vec<Vec<f64>> that creates an n×n identity matrix (1.0 on the diagonal, 0.0 everywhere else) using vec![0.0; n] rows and then writing the diagonal cells.
  • Implement transpose(matrix: &Vec<Vec<f64>>) -> Vec<Vec<f64>> that returns a new matrix where rows and columns are swapped. Use nested iteration and verify that transpose(transpose(m)) == m for a non-square matrix.
  • Rewrite the 2D matrix using a flat Vec<f64> of length rows * cols and implement get(row: usize, col: usize) and set(row: usize, col: usize, val: f64) accessors that compute the linear index internally. Benchmark it against the Vec<Vec<f64>> version for a large matrix to observe cache effects.
  • Open Source Repos