Array.make and Array.make_matrix — Multi-dimensional Arrays
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
Array.make 5 0 maps to Rust's vec![0; 5] — both create a flat initialized array but express it through different syntaxArray.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 Rustmatrix.(row).(col) <- value; Rust uses matrix[row][col] = value after binding with let mutmut to allow any mutation, while OCaml arrays are always mutable by constructionArray.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 languagesCode 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
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.let mut before the first write, and the compiler rejects any attempted mutation of a non-mut binding at compile time..<- operator matrix.(r).(c) <- v; Rust uses index notation with assignment matrix[r][c] = v after the binding is declared mut.'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);
}
}
}
}#[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
| Concept | OCaml | Rust |
|---|---|---|
| 1D array | Array.make : int -> 'a -> 'a array | vec![value; n] or fn make<T: Clone>(n: usize, v: T) -> Vec<T> |
| 2D matrix | Array.make_matrix : int -> int -> 'a -> 'a array array | vec![vec![value; cols]; rows] → Vec<Vec<T>> |
| Index access | arr.(i) | arr[i] |
| Mutation | arr.(i) <- v (in-place) | arr[i] = v (in-place) |
| Bounds check | runtime exception | runtime panic (in debug), or use .get_mut() for Option |
Key Insights
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.matrix.(1).(2) <- 42.0 in OCaml, matrix[1][2] = 42.0 in Rust). OCaml arrays are mutable by default; Rust requires mut.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.'a, Rust uses T: Clone. The Clone bound is needed because vec![value; n] clones the value n - 1 times.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
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.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.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.