Arrays and Tuples
Arrays and tuples are Rust's two primitive compound types.
Tuples:
- Can group different data types together into one compound type
- Have a fixed size; once defined, it cannot grow or shrink.
Example:
fn main() { let person = (String::from("Olu"), String::from("Shiyanbade"), 42, 1.85, String::from("+447111222333")); println!("{:?}", person ); }
In the above the tuple's type is implicitly determined by Rust based on the values the tuple holds.
In this case, it is (String, String, i32, f64, String)
Accessing elements in a tuple
- You can use destructuring to access the individual elements of tuple:
fn main() { let person = (String::from("Johnny"), String::from("Walker"), 42, 1.85, String::from("+447111222333")); let (first_name, last_name, age, height, phone_number) = person; println!("{} {} {} {} {}", first_name, last_name, age, height, phone_number); }
- You can also use a period followed by the index of the tuple element you want to access e.g. (First index is 0):
fn main() { let person = (String::from("Johnny"), String::from("Walker"), 42, 1.85, String::from("+447111222333")); println!("{} {} {} {} {}", person.0, person.1, person.2, person.3, person.4); }
- Note that once you de-construct a tuple, you can't use the
tuple_name.index
if the value at that index is a value that lives on the heap i.e., a Droppable value such asString
because deconstructing moves the value. See Moving
fn main() { let person = (String::from("Olu"), String::from("Shiyanbade"), 42, 1.85, String::from("+447111222333")); let (first_name, last_name, age, height, phone_number) = person; //line below will error because String is moved during the deconstruction above //println!("{}", person.0); //this line is fine because i32 and f64 are not Droppable types i.e., the are copied when reassigned to a variable println!("{} {}", person.2, person.3); }
Modifying a tuple's contents
You can use the tuple.index
syntax to modify a tuple's element. For example,
fn main() { let mut person = ("Johnny", "Walker", 42); println!("{:?}", person); person.2 = 88; person.1 = "James"; println!("{:?}", person); }
Passing tuple to a function or reassigning a tuple
- When tuple contains only types that implement Copy trait, the tuple is copied. You can see this in the example below that
when we modify the passed in tuple in
change_names
function, and then callprint_names
again in themain
function, the original tuple's contents is not affected.
fn print_numbers(nums:(i32, i32, i32)) { print!("{:?}", nums); } fn change_numbers(mut nums:(i32, i32, i32)) { nums.0 = 6; nums.1 = 5; print_numbers(nums); } fn print_names(names: (&str, &str, &str)) { print!("{:?}", names); } fn change_names(mut names: (&str, &str, &str)) { names.0 = "James"; names.1 = "Peter"; names.2 = "John"; print_names(names); } fn main() { let count_down = (3, 2, 1); print_numbers(count_down); println!(); change_numbers(count_down); println!(); print_numbers(count_down); println!(); let names = ("Timi", "Tommy", "Jones"); print_names(names); println!(); change_names(names); println!(); print_names(names); }
- When tuple contains only types that implement Drop trait, when you pass that tuple to a function, the contents of the tuple are moved
and cannot be used thereafter. In the below, the second call to
print_names
will cause a compile error.
fn print_names(names: (String, String, String)) { print!("{:?}", names); } fn main() { let names = (String::from("Timi"), String::from("Tommy"), String::from("Jones")); print_names(names); println!(); //The line below will cause a compile error because the contents of the tuple // was moved on the print_names() call above. print_names(names); }
- When tuple contains types that implement Copy trait as well as types that implement Drop trait, Rust compiler will still throw an error and rightly so i.e., although you have a value that implements Copy trait in there, there's no way for Rust to guarantee that you're not going to used the dropped values at runtime. Thus, it makes sense to disallow the whole thing altogether.
fn print_person(names: (String, String, i32)) { print!("{:?}", names); } fn main() { let person = (String::from("Timi"), String::from("Tommy"), 42); print_person(person); println!(); //The line below will cause a compile error because the contents of the tuple // was moved on the print_names() call above. print_person(person); }
Arrays:
- An array's data is allocated on the stack (see arrays)
- All elements in an array must be of the same type
- Arrays have a fixed length
Initializing an array
fn main() { let fruits = ["apple", "pear", "orange", "blueberry"]; }
An array type is indicated using square brackets, the data type of each array element, a semicolon followed by the number of elements in the array.
In the above snippet the type of the array is implicitly set by rust to [&str; 4]
i.e., an array containing four string slices.
However, we can also explicitly specify the type like so:
fn main() { let numbers: [i32; 3] = [1, 2, 3]; println!("{:?}", numbers); }
It is also possible to initialise all the elements of the array to the same value as shown below. In this case, the data type of the array is determined using the value you specified for initialisation.
fn main() { let fruits = ["apple"; 10]; //Array's type is determined to be [&str; 10] println!("{:?}", fruits); let numbers = [5; 10]; // Array's type is determined to be [i32; 10] println!("{:?}", numbers); }
Passing array to a function or reassigning an array
- When array contains only types that implement Copy trait: when you pass an array containing only types such as integers, floats, booleans, char and your own custom types, that implement the Copy trait to a function, that array is copied.
fn print_numbers(arr:[i32;5]) { for num in arr { print!("{num} ") } } fn change_numbers(mut arr:[i32;5]) { arr[0] = 6; arr[1] = 5; print_numbers(arr); } fn print_names(names: [&str;3]) { for name in names { print!("{name} "); } } fn change_names(mut names:[&str;3]) { names[0] = "James"; names[1] = "Peter"; names[2] = "John"; print_names(names); } fn main() { let count_down = [5, 4, 3, 2, 1]; print_numbers(count_down); println!(); change_numbers(count_down); println!(); print_numbers(count_down); println!(); let names = ["Timi", "Tommy", "Jones"]; print_names(names); println!(); change_names(names); println!(); print_names(names); }
In the above, you can see that the count_down
array is still the same after the call to mutate_and_print_array
function.
- When array contains only types that implement Drop trait: when you pass an array containing only types that implement the Drop trait such as String or your own custom types, to a function, that array's contents are moved by default.
fn print_names(names: [String;3]) { for name in names { print!("{name} "); } } fn main() { let names = [String::from("Timi"), String::from("Tommy"), String::from("Jones")]; print_names(names); //move occurs here println!(); //The snippet below will cause a compile error. print_names(names); }