Lessons learned in Rust with Array and Vector

November 24, 2023 / 6 min read

rusttypescriptarraysvectors

As a developer familiar with JavaScript and TypeScript, delving into Rust felt like stepping into a new realm of elegance and precision. The syntax captivated me, sparking a curiosity to explore its depths. In my Rust learning journey, I ventured from the familiar territory of TypeScript to crafting a simple 'add array' function. Little did I know, this exploration would lead me to the fascinating realms of vectors and arrays. Join me as I share the discoveries and insights gained.

TypeScript

Let's start with the full implementation of the add array function in TypeScript that I wrote first.

typescript
function addArrays(arr1: number[], arr2: number[], rarr: number[]): void {
for (let i = 0; i < arr1.length; i++) {
rarr[i] = arr1[i] + arr2[i];
}
}
function initArray(arr: number[], startVal: number): void {
for (let i = 0; i < arr.length; i++) {
arr[i] = startVal + i;
}
}
const size: number = 5;
let array1: number[] = Array.from({ length: size }, () => 0);
let array2: number[] = Array.from({ length: size }, () => 0);
let resultantArray: number[] = Array.from({ length: size }, () => 0);
initArray(array1, 0);
initArray(array2, 10);
addArrays(array1, array2, resultantArray);
console.log(resultantArray); // [10, 12, 14, 16, 18]

Let's break down the code.

typescript
function addArrays(arr1: number[], arr2: number[], rarr: number[]): void {
for (let i = 0; i < arr1.length; i++) {
rarr[i] = arr1[i] + arr2[i];
}
}
  • This function takes three parameters: arr1, arr2, and rarr, all of which are arrays of numbers.
  • It iterates through the arrays using a for loop and adds the corresponding elements of arr1 and arr2, storing the result in rarr.
typescript
function initArray(arr: number[], startVal: number): void {
for (let i = 0; i < arr.length; i++) {
arr[i] = startVal + i;
}
}
  • This function initializes an array arr with values starting from startVal and incrementing by 1 for each index.
typescript
const size: number = 5;
let array1: number[] = Array.from({ length: size }, () => 0);
let array2: number[] = Array.from({ length: size }, () => 0);
let resultantArray: number[] = Array.from({ length: size }, () => 0);
  • size is a constant variable set to 5, representing the size of the arrays.
  • array1, array2, and resultantArray are initialized as arrays of length size filled with zeros using Array.from().
typescript
initArray(array1, 0);
initArray(array2, 10);
addArrays(array1, array2, resultantArray);
console.log(resultantArray); // [10, 12, 14, 16, 18]
  • The addArrays function is called with array1, array2, and resultantArray.
  • The final resultantArray is logged to the console.

If you familiar with TypeScript I'm sure that this will be easy for you to understand. Now let's rewrite this in Rust.

Rust 🦀

rust
fn add_arrays(arr1: &mut Vec<i32>, arr2: &mut Vec<i32>, rarr: &mut Vec<i32>) {
for i in 0..arr1.len() {
rarr[i] = arr1[i] + arr2[i];
}
}
  • This function takes three parameters: arr1, arr2, and rarr, all mutable references to vectors of 32-bit signed integers.
  • It iterates through the vectors using a for loop and adds the corresponding elements of arr1 and arr2, storing the result in rarr.
rust
fn init_array(arr: &mut Vec<i32>, start_val: i32) {
for i in 0..arr.len() {
arr[i] = start_val + i as i32;
}
}
  • This function initializes a vectors (arr) with values starting from start_val and incrementing by 1 for each index.
rust
fn main() {
let size: usize = 5;
let mut array1: Vec<i32> = vec![0; size];
let mut array2: Vec<i32> = vec![0; size];
let mut resultant_array: Vec<i32> = vec![0; size];
init_array(&mut array1, 0);
init_array(&mut array2, 10);
add_arrays(&mut array1, &mut array2, &mut resultant_array);
println!("{:?}", resultant_array);
}
  • The main function is the entry point of the Rust program.
  • It declares a constant variable size set to 5, representing the size of the vectors.
  • Mutable vectors array1, array2, and resultant_array are initialized with zeros using vec![0; size]. vec! is a macro.
  • The init_array function is called to initialize array1 starting from 0 and array2 starting from 10.
  • The add_arrays function is called with mutable references to array1, array2, and resultant_array.
  • The final resultant_array is printed to the console using println!("{:?}", resultant_array).

Rust uses vectors and borrows/mutable references to achieve similar functionality while adhering to its ownership and borrowing system.

Exploring Rust, I discovered a simpler approach. The function designed for vectors found a better fit in fixed array, especially since dynamic expansion wasn't needed. Now, let's walk through the process of rewriting the aforementioned function using simple fixed size arrays.

rust
fn add_arrays<const N: usize>(arr1: &mut [i32; N], arr2: &mut [i32; N], rarr: &mut [i32; N]) {
for i in 0..N {
rarr[i] = arr1[i] + arr2[i];
}
}
  • In this version, the function uses arrays [i32; N] instead of vectors Vec<i32>.
  • The function signature indicates that the size of the arrays is determined at compile time with the generic parameter const N: usize, similar to the TypeScript's generic.
  • It iterates through the arrays using a for loop and adds the corresponding elements of arr1 and arr2, similar to the Vec version.
rust
fn init_array(arr: &mut [i32], start_val: i32) {
for i in 0..arr.len() {
arr[i] = start_val + i as i32;
}
}
  • This function initializes a mutable reference to an array [i32] instead of a vectors Vec<i32>.
  • The initialization process is similar, filling the array with values starting from start_val and incrementing by 1 for each index.
rust
fn main() {
const SIZE: usize = 5;
let mut array1 = [0; SIZE];
let mut array2 = [0; SIZE];
let mut resultant_array = [0; SIZE];
init_array(&mut array1, 0);
init_array(&mut array2, 10);
add_arrays(&mut array1, &mut array2, &mut resultant_array);
println!("{:?}", resultant_array);
}
  • In the main function, arrays [i32; N] are used instead of vectors Vec<i32>.
  • The arrays are initialized with zeros using the array initializer [0; SIZE], a concise alternative to vec![0; SIZE].
  • The initialization and manipulation process with arrays is similar to the Vec example, showcasing Rust's flexibility with different data structures.

Comparisons

Arrays vs. Vectors: Arrays have a fixed size determined at compile time, while vectors can dynamically grow or shrink. Coming from JavaScript and TypeScript, the natural inclination might be to default to vectors, as arrays are commonly used in these languages. However, exploring arrays in Rust opens up a world of possibilities, offering a more precise and statically-typed alternative for scenarios where the size and type of elements are known at compile time. Arrays bring a level of predictability and performance that aligns with Rust's focus on memory safety without sacrificing efficiency. In this transition, embracing the unique features of arrays can enhance code clarity and align more closely with Rust's ownership model and compile-time guarantees.


As a Rust beginner, the journey of discovering the nuances between vectors and arrays has been both enlightening and empowering. Looking ahead, I am eager to share more about my Rust learning experiences. The language's syntax and elegance have captivated me, and I'm excited to explore further, uncovering more insights and challenges along the way. Stay tuned for more tales from my Rust learning adventure!