In this video, learn how to store a collection of multiple values with the same data types in an array using Rust. This is essential to learn because arrays are a commonly used data type for storing sequential data points in Rust.
- [Instructor] In addition to the primitive data types we've looked at so far which can each be used to represent a single scalar value, Rust also has a few compound data types which you can use to store multiple values. An array can be used to hold a collection of elements that all have the same data type. The elements are stored sequentially in a specific order. One after another, in a contiguous section of memory. In Rust, arrays have a fixed length which the compiler needs to know to determine how much memory will be needed to hold the array. You cannot dynamically resize an array in Rust. Conceptually, I like to use the analogy that an array is like a row of parked cars because all of the parking spots hold the same type of thing. A car, the spots are all indexed with sequential numbers so you can locate your car along the row. Notice that the number in the first spot here is zero. Rust, like many other programming languages uses zero indexing which means the first element has an index number of zero and the next one after that is one, then two and so on. Since array elements are stored in order, one common use for arrays is to hold time series data or a sequence of values indexed in time order that might represent something like the signal shown here. One way to declare a new array in Rust is to enclose a comma separated list of initial values inside of square brackets. So to declare an array containing the characters A, B, and C, I could type let letters equal A, B and C. Now, to access individual elements in an array to retrieve or modify their value, we use square brackets and the numeric index corresponding to that element. So if I want to retrieve the first letter from this array, I would type letters square brackets and then the index value zero. I'll add a print macro after that to look at the first letter that was retrieved. And when I run this program from the output, we can see that the array element at index zero was the letter A which was the first element in the array. If we want to modify values in an array, we'll first need to make sure that it's declared as mutable. Then we can use the equal symbol to assign a new value to a specific index location. This will change the first element to be the letter X before retrieving and printing it. And if I run this program again, now we see that the first letter is X. It's worth noting that we can only replace elements of this array with other characters, because all of the elements must be the same data type. And that data type must be known at compile time along with the length of the array. The compiler can look at the values we use to initialize this array online too and tell that it contained three characters, but what if we don't have an initial set of values to use when declaring the array? Rust allows us to declare new variables without assigning an initial value as long as we specify the data type. For an array of numbers, that would look like this, let numbers i32, 5. This will create an array named numbers to hold signed 32 bit integers and it has a length of five elements. Those two pieces of information, the data type and number of elements enable the compiler to determine how much space in memory the program will need to hold the array even though we haven't assigned it any values yet. Now, let's see what happens if we try to access a value in that uninitialized array by indexing the last value which because of zero indexing, will be at index number four for this five element long array. When I try to run that program, the compiler throws an error because it detects that we're trying to access an uninitialized memory location in the array which has the potential to cause some nasty bugs. Although Rust lets you declare variables without an initial value, the compiler always checks to make sure you assign the variable a value before trying to read from it. For simplicity, let's initialize this array to have all zeroes. Now, listing out five zeros like this isn't too much trouble, but if we wanted to initialize a much longer array with hundreds or thousands of elements this way, individually typing out all those zeros would be a tedious waste of time. A more efficient way to initialize an array with all the same value is to use a repeat expression. This will produce an array containing five copies of the value zero. When I run this program, we can see the displayed output for the last number is zero. Now, let's see what happens if we try to index beyond the length of the array by changing the index online nine from four to five. The numbers array is only five elements long. So it's valid index values are zero through four. When I try to run this program, it fails compilation because the compiler recognized that the index is out of bounds before the program could even run. That's fairly obvious to see in this situation using a fixed value for the index, but let's see what happens if we try to use a variable to index the array. I'll declare a new variable named index and assign its value to be the length of the numbers array which will be five by using the length function. We'll cover that in more detail in a later video then I'll change the index online ten to use that variable. One thing I'd like to point out here is that the data type we're using index the array is not one of the normal integer types we covered in an earlier video. Instead, a raise in Rust must be indexed using a special data type called usize. The length of this usize data type will be determined by the compiler based on the number of bytes that are needed to address memory and to target architecture you're compiling the program for. For example, when compiling for a 32 bit target, a usize variable would compile to be four bytes long. Whereas compiling for a 64 bit target would make usize eight bytes long. Now, let's try running this program and see what happens. It successfully compiles this time. But during execution, when it reaches line tin and tries to index the numbers array, it panics with a runtime error that the index was out of bounds and the program exits. This may seem obnoxious but it's actually Rust playing it safe and protecting us because it's safer to knowingly exit the program here, than allow it to continue running after making an invalid memory access. Other low-level languages like C and C plus plus, do not prevent you from indexing array values that are out of bounds. And that's a common source of bugs that are painful to track down and can produce invalid results that you may not even realize are wrong.