Rust for the Novice Programmer/Tuples and Structs

Tuples and Structs edit

Let's say we wanted to return multiple values from a function. As an example, let's make a function that gets the minimum, maximum value as well as whether the mean(average) was less than a certain threshold out of a vector of numbers. We can't return a vector or array since the third value we want to return will be a boolean, while the other two will be integers and vectors and arrays can only store values of the same type. Thus, the easiest solution is to return a tuple.

A tuple is just a collection of values, organised with () parentheses. So our function will be like

 fn get_min_max_mean_below_threshold(input: Vec<i32>, threshold: i32) -> (i32, i32, bool) {
     //to do
 }

So the function takes in a vector of input values and a threshold as an input, and returns a tuple of two integers and one boolean as an output. The reason to have this as one function rather than three separate functions is that we can calculate all three at the same time while only going through the numbers once. If we had three separate functions, it would be slower to go through all the numbers three times.

 fn get_min_max_mean_below_threshold(input: Vec<i32>, threshold: f64) -> (i32, i32, bool) {
     let mut sum = 0;
     let mut min = i32::MAX;
     let mut max = i32::MIN;
 
     for index in 0..input.len() {
         let value = input[index];
         sum += value;
         if value < min {
             min = value;
         }
         if value > max { 
             max = value;
         }
     }

     let mean = sum as f64 / input.len() as f64;
     let mut below_threshold = false;
     if mean < threshold {
         below_threshold = true;
     }
     return (min, max, below_threshold);
 }

The whole code is included for you to look at, examine and try out. I will note that the min and max must have a value to begin with so they are set to the maximum and minimum values initially so pretty much any value inside the vector will overwrite them. The main thing for tuples is that we construct a tuple at the end by putting it in () brackets and return it from the function. The order is important as we use it to get the values out of the tuple. In our main function, there are two ways of getting values out. Here is the first way:

 fn main() {
     let values = get_min_max_mean_below_threshold(vec![10, 3, 6, 2, 7, 14], 10.0);
     println!("The minimum is {}", values.0);
     println!("The maximum is {}", values.1);
     println!("Whether the mean was below the threshold was {}", values.2);
 }

Note that we include other things in the text inside the println!() here to provide context to the different values. The value after the comma will be inserted where the {} is. We get the values out of the tuple by referring to their index in the tuple, again like arrays starting from 0. However, unlike arrays and vectors we cannot use variables to get a value out, so we couldn't do:

 let index = 1;
 let value = values.index;
This will not work, if you want something like this you will want an array most likely. There is another way of getting values out of a tuple:
  fn main() {
     let (min, max, below_threshold) = get_min_max_mean_below_threshold(vec![10, 3, 6, 2, 7, 14], 10.0);
     println!("The minimum is {}", min);
     println!("The maximum is {}", max);
     println!("Whether the mean was below the threshold was {}", below_threshold);
 }

This is called destructuring, since we mimic the structure of the tuple to get the values out into variables that act like any other variables. Which option you choose to get values out is up to you, and may depend on the situation for whatever is most appropriate.

However, there is a clear disadvantage to this. Imagine we wanted to also get other values like the mode, or the standard deviation(if you don't know what these are that's ok). A tuple that has too many values is unwieldy since we have to remember the order of all the values. The best way to have a more structured view of a set of data is called a struct. We construct/declare a struct like so:

 struct VectorValues {
     minimum: i32,
     maximum: i32,
     is_below_threshold: bool,
 }

Since this is a declaration, we just specify the types that will be put inside. Then we change our getting values function to:

 fn get_min_max_mean_below_threshold(input: Vec<i32>, threshold: f64) -> VectorValues {
     //the inside is the same as above except for the last line
     let values = VectorValues {
         minimum: min,
         maximum: max,
         is_below_threshold: below_threshold,
     };
     return values;
 }

Now this means in our main function we can do:

 fn main() {
     let values = get_min_max_mean_below_threshold(vec![10, 3, 6, 2, 7, 14], 10.0);
     println!("The minimum is {}", values.minimum);
     println!("The maximum is {}", values.maximum);
     println!("Whether the mean was below the threshold was {}", values.is_below_threshold);
 }

Now we refer to the values inside the struct by the names we assigned to them after a dot, which should hopefully be useful to us, more memorable and easier to understand when reading.

Next: Enums