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. To declare an array with explicit data types, specify the data type followed by a semicolon and the array's size within square brackets. The values for each element in the array must match this explicitly declared data type. Rust can also infer the data types, eliminating the need to explicitly declare data types.
Here’s how you can create arrays with and without explicit data types:
Rust1fn main() { 2 // Creating Array with Data Type 3 let array_with_type: [i32; 4] = [1, 2, 3, 4]; 4 // Creating Array without Data Type (type inference) 5 let array_without_type = [5, 6, 7, 8]; 6 7 println!("Array with type: {:?}", array_with_type); // Prints: [1, 2, 3, 4] 8 println!("Array without type: {:?}", array_without_type); // Prints: [5, 6, 7, 8] 9}
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 the elements’ type.
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:
Rust1fn main() { 2 let array = [1, 2, 3, 4]; 3 println!("First element: {}", array[0]); // Prints: First element: 1 4 println!("Fourth element: {}", array[3]); // Prints: Fourth element: 4 5}
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.
Rust1fn main() { 2 let mut mutable_array = [9, 10, 11, 12]; 3 mutable_array[2] = 42; // Modifying the third element 4 println!("Mutable array: {:?}", mutable_array); // Prints: Mutable array: [9, 10, 42, 12] 5}
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:
Rust1fn main() { 2 let array_with_copy = [1, 2, 3, 4]; // Array with Copy type data 3 4 // Copy data 5 let copy_array = array_with_copy; // Elements are copied 6 println!("array_with_copy: {:?}", array_with_copy); // Prints: array_with_copy: [1, 2, 3, 4] 7 println!("copy_array: {:?}", copy_array); // Prints: copy_array: [1, 2, 3, 4] 8 9 // Non-copy data 10 let array_with_non_copy = [String::from("Hello"), String::from("World")]; // Array with data that cannot be copied 11 let first_elem = &array_with_non_copy[0]; // Creates a reference 12 println!("Accessed element by reference: {}", first_elem); // Prints: Accessed element by reference: Hello 13 let invalid_copy = array_with_non_copy[0]; // Causes an error. You cannot move ownership of array elements 14 15 // Ownership Transfer 16 let non_copy_array = array_with_non_copy; // Ownership moves 17 println!("{:?}", array_with_non_copy); // Causes an error 18}
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:
Rust1fn main() { 2 let array = [1, 2, 3, 4]; 3 let slice = &array[1..3]; // Slicing the array 4 println!("Slice from array: {:?}", slice); // Prints: Slice from array: [2, 3] 5 6 let full_slice = &array[..]; // Full slice of the array 7 println!("Full slice of array: {:?}", full_slice); // Prints: Full slice of array: [1, 2, 3, 4] 8 9 // Modifying slice elements through a mutable slice 10 let mut array_for_slice = [10, 20, 30, 40]; 11 { 12 let slice = &mut array_for_slice[1..3]; 13 slice[0] = 25; // Modifying the slice 14 slice[1] = 35; // Modifying the slice 15 } 16 println!("Array after modifying slice: {:?}", array_for_slice); // Prints: Array after modifying slice: [10, 25, 35, 40] 17}
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.
Rust1fn main() { 2 let array_with_non_copy = [String::from("Hello"), String::from("World")]; 3 display_array_reference(&array_with_non_copy); 4 println!("After display_array_reference: {:?}", array_with_non_copy); // Prints: After display_array_reference: ["Hello", "World"] 5 6 let array_with_copy = [1, 2, 3, 4]; 7 display_array_copy(array_with_copy); 8 println!("After display_array_copy: {:?}", array_with_copy); // Prints: After display_array_copy: [1, 2, 3, 4] 9 10 display_array_ownership(array_with_non_copy); 11 // println!("After display_array_ownership: {:?}", array_with_non_copy); // Causes error 12} 13 14fn display_array_reference(arr: &[String; 2]) { 15 println!("In display_array_reference: {:?}", arr); // Prints: In display_array_reference: ["Hello", "World"] 16} 17 18fn display_array_copy(arr: [i32; 4]) { 19 println!("In display_array_copy: {:?}", arr); // Prints: In display_array_copy: [1, 2, 3, 4] 20} 21 22fn display_array_ownership(arr: [String; 2]) { 23 println!("In display_array_ownership: {:?}", arr); // Prints: In display_array_ownership: ["Hello", "World"] 24}
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!