Solution

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

fn transpose(matrix: [[i32; 3]; 3]) -> [[i32; 3]; 3] {
    let mut result = [[0; 3]; 3];
    for i in 0..3 {
        for j in 0..3 {
            result[j][i] = matrix[i][j];
        }
    }
    result
}

fn main() {
    let matrix = [
        [101, 102, 103], // <-- the comment makes rustfmt add a newline
        [201, 202, 203],
        [301, 302, 303],
    ];

    println!("Original:");
    for row in matrix {
        println!("{row:?}");
    }

    let transposed = transpose(matrix);

    println!("\nTransposed:");
    for row in transposed {
        println!("{row:?}");
    }
}
  • Array Types: The type [[i32; 3]; 3] represents an array of size 3, where each element is itself an array of 3 i32s. This is how multi-dimensional arrays are typically represented in Rust.
  • Initialization: We initialize result with zeros ([[0; 3]; 3]) before filling it. Rust requires all variables to be initialized before use; there is no concept of “uninitialized memory” in safe Rust.
  • Copy Semantics: Arrays of Copy types (like i32) are themselves Copy. When we pass matrix to the function, it is copied by value. The result variable is a new, separate array.
  • Iteration: We use standard for loops with ranges (0..3) to iterate over indices. Rust also has powerful iterators, which we will see later, but indexing is straightforward for this matrix transposition.
  • Mention that [i32; 3] is a distinct type from [i32; 4]. Array sizes are part of the type signature.
  • Ask students what would happen if they tried to return matrix directly after modifying it (if they changed the signature to mut matrix). (Answer: It would work, but it would return a modified copy, leaving the original in main unchanged).