Hello again, Rustaceans! 🦀

Today, we’re going to explore arrays in Rust. Arrays are a fundamental data structure that allow us to store multiple values of the same type in a single variable. They’re incredibly useful for organizing data and making our code more efficient and readable.

Let’s start by creating an array. In Rust, arrays are declared using the syntax let variable_name : [data_type; size] . This means you specify the type of data the array will hold, and the size of the array.

There are several ways to initialize arrays in Rust. One common method is to initialize them with specific values. Here’s an example:

let my_numbers: [u32; 5] = [1, 2, 3, 4, 5];

In this example, my_numbers is an array that holds five unsigned 32-bit integers. We’ve specified the values of the array elements directly in the declaration.

But what if you want to initialize all elements of an array to the same value? Rust has got you covered:

let my_numbers = [0; 5];

In this case, my_array is an array of five integers, all initialized to 0. This is a handy shortcut when you need an array with a default value.

Remember, the size of arrays in Rust is fixed at compile time, and each element in the array must be of the same type. This can be different from other datatypes like vector that can be resized dynamically.

💡
Note that you don't need to specify the array data type and size if you are initializing it right away. Rust can infert it.

Let's print our array. We can use a for loop to iterate over the array and print each element. Here’s how you might do it:

fn main() {
    let my_numbers = [1, 2, 3, 4, 5];
    for number in my_numbers {
        print!("{} ", number);
    }
}

However, if you have numerous arrays in your codebase, repeating this process for each one can become cumbersome. There is a much better way to do this. If you recall our discussion on the Debug and Display traits a few days back (here’s the link if you need a refresher), we can leverage these traits to print our arrays. Instead of the Display placeholder, we can use the Debug placeholder, or we can implement the Display trait for the array. Here’s how you can do it:

fn main() {
    let my_numbers = [1, 2, 3, 4, 5];
    println!("{:?} ", my_numbers);
}

In this example, we’re using the Debug placeholder {:?} to print the entire array in one line. This is a much better approach when dealing with arrays in Rust.

Accessing elements in an array is straightforward. We use indexing, just like in most other programming languages.

println!("The first number: {}", my_numbers[0]);

Now, here’s where Rust arrays differ from arrays in C. In Rust, arrays are bound-checked. This means that if you try to access or modify an element at an index that doesn’t exist, Rust will throw a compile-time error. Let's try to compile this code

#include <stdio.h>

int main() {
    int a[3] = {0};
    printf("%d", a[1000]);
}

In this code, we’re declaring an array a with a length of 3 and then attempting to access the element at index 1000, which is well beyond the bounds of the array. The behavior of this code can vary widely depending on where it’s run, when it's run. It might cause an immediate crash, it might cause problems later, or it might not seem to cause any issues at all. This is because it’s accessing memory that it shouldn’t be, and what’s in that memory can vary. What’s surprising is that the gcc compiler doesn’t even issue a warning about this in its default settings.

Now, let’s see how this scenario would play out in Rust.

fn main() {
    let mut my_numbers = [0;5];
    my_numbers[1000] = 100;
}

Indeed, it’s not that C compilers are incapable of flagging such issues. By enabling certain options in compilers or using static analysers, you can check for out-of-bounds access. For instance, in gcc, you can use the -Warray-bounds option for this purpose. Many of the safety features that Rust enforces do exist in C++, but the key difference is that they’re not enforced by default. Rust, on the other hand, enforces these safety measures as part of the compilation process.

This is what sets Rust apart. The fact that you would need to use a multitude of tools to achieve the level of safety guarantees provided by the Rust compiler speaks volumes about Rust’s advantages.

The numerous processes and tools required to ensure memory safety in C++ can indeed hamper productivity. With Rust, these safety measures are built-in, allowing developers to write safer code more efficiently.

Another thing to remember about Rust arrays is that they are considered types. This means that arrays of different sizes or different element types are considered different types. Therefore, you cannot directly assign an array of one type to an array of another type:

fn main() {
    let mut my_numbers = [0;5];
    let another_array = [10, 20, 30];
    my_numbers = another_array;  // This would cause a compile-time error due to mismatched types
}

Conclusion

Today, we took another step in our Rust journey by exploring arrays. Understanding how to work with arrays is crucial when it comes to organizing data and making our code more efficient and readable.

You can find all the code examples from today’s session on the inpyjama GitHub page:

rust/day9 at main · inpyjama/rust
Contribute to inpyjama/rust development by creating an account on GitHub.



See you tomorrow for more Rust adventures!