Welcome back! Today, we're diving into arrays in Rust. In our last lesson, we explored tuples, a way to group different types of data into a single compound type. Arrays are somewhat similar but come with their own unique set of characteristics and benefits.
An array in Rust is a collection of elements of the same type, stored in a contiguous block of memory. This can be especially useful when you have a fixed-size collection of elements that you need to manage efficiently. Unlike tuples, every element of an array must be the same data type.
Imagine a scenario where you need to store the temperatures recorded for each day of the week. An array can be an ideal candidate for this. Let's explore arrays in Rust in more detail!
There are two ways to declare arrays in Rust:
- With explicit type and length: You specify both the data type of elements and the array's size.
- With type inference: You let Rust figure out both the data type and length based on the values you provide.
When declaring an array explicitly, you need to specify the data type of elements, include a semicolon followed by the array's size, and place both inside square brackets (like [i32; 4]
). You must also ensure all values match the declared type. When using type inference, you simply provide the values in square brackets, and Rust automatically determines both the type and length for you.
Here's how you can create arrays with and without explicit type and length declarations:
In the code above:
array_with_type
is an array explicitly typed as having four elements, all of typei32
.array_without_type
relies on type inference to determine both the elements' type and the array's length (4 in this case).
Recall, to print the values of an array, we use {:?}
in the println!
statement.
In Rust, you can access the elements of an array using index notation. Arrays are zero-indexed, meaning the first element of the array is at index 0. To access the value of an array, use the array name followed by square brackets containing the index number. Here’s how you can do it:
In this example:
- We accessed the first element using
array[0]
. - We accessed the fourth element using
array[3]
.
In Rust, arrays are by default immutable. Using the mut
keyword, we can modify the elements of an array. Keep in mind the data type of new values must be the same as the original value.
In this code:
- We created a mutable array
mutable_array
. - We modified the third element to
42
usingmutable_array[2] = 42
.
Understanding whether data in arrays can be copied or moved is crucial for effective Rust programming. If all the elements in an array implement the Copy
trait, the array itself will also implement the Copy
trait. Assigning an element of a non-copy Array to a variable is not allowed. Instead, you must use a reference. Here's an example:
Array with Copy Data
array_with_copy
is an array of integers, and sincei32
implements theCopy
trait, this entire array also implements theCopy
trait.- When
array_with_copy
is assigned tocopy_array
, each element is copied.
Array with Non-Copy Data
- The
array_with_non_copy
array includesString
elements, which do not implement theCopy
trait and thus transfer ownership when assigned. - The line
&array_with_non_copy[0]
creates a reference to the first element of the array - The line
array_with_non_copy[0]
causes an error because you cannot move ownership of elements in a non-Copy array
Ownership Transfer
- Ownership of
array_with_non_copy
is moved tonon_copy_array
, makingarray_with_non_copy
invalid.
Slices in Rust provide a way to reference a contiguous sequence of elements from an array. They are particularly useful for working with subsections of an array without needing to create a new array. To create an array slice, use a reference to the array followed the starting index up to, but not including the ending index. To create a full slice of an array, simply place ..
inside the brackets. Here’s how you can create and use slices:
In this code:
- We created a slice that references elements two and three of
array
. - We created a
full_slice
that references the entire array. - We modified a section of
array_for_slice
through a mutable slice.
Arrays can be passed to functions in Rust, making it possible to work with fixed-size collections efficiently. Similar to tuples, arrays can be passed by reference or by value, depending on whether you want to transfer ownership or simply allow the function to read the data. The rules for passing arrays as function parameters are:
- Passing a reference to an array does not transfer ownership.
- Passing an array by value copies the array if its elements implement the Copy trait.
- Arrays composed of non-copy elements transfer ownership if passed by value.
In this code:
-
The function
display_array_reference
takes a reference to an array with twoString
elements. This allows the function to read the array without taking ownership, so the array remains available in the main function. -
The function
display_array_copy
takes an array with fouri32
elements. Sincei32
implements the Copy trait, the data is copied when passed to the function. -
The function
display_array_ownership
takes ownership of an array with twoString
elements. This consumes the array, making it unavailable for further use in the main function. Uncommenting the lastprintln!
statement inmain
causes an error because the array's ownership has been moved to the function.
Congratulations! You've taken a significant step in your Rust journey by understanding how to work with arrays. We've covered creating arrays, accessing and modifying their elements, handling copy and non-copy data types, and using slices. Mastering arrays is crucial for efficiently handling collections of data in your Rust programs.
As we move forward, your understanding will be solidified through hands-on practice. Get ready to apply what you've learned in the exercises that follow. Happy coding!
