Rust for the Novice Programmer/Number Conversions

Number Conversions edit

If we have a number of one type (e.g. u8) and we want to convert it to a number of a different type (e.g. i32), how would we do this? An important principle of converting between number types is that increasing the number of bits will always be safe. However, going the other way can be risky since we are losing bits which may or may not carry information. Thus, if we are doing a 'safe' transformation upwards, then there are some easy ways to do it.

 fn main() {
     let number1: u8 = 10;
     let number2 = i32::from(number1);
     let number3: i32 = number1.into();
 }

At the end, all of these will be 10. Note that for the third one, we specified the type of number3 as i32. If we didn't do this, it wouldn't compile since the compiler must know the type of a variable. If we used it later on in a place where an i32 was required then the compiler could infer that it is an i32. This is called type inference and is very helpful in reducing the amount of unnecessary code we write.

If we want to go the other way and convert from a larger type into a smaller type, the easiest way is to use the keyword 'as' to cast. This is risky since if the number is something the smaller type can't store, then you will get strange behaviour. For example:

 fn main() {
     let number1 = -10i32;
     let number2 = number1 as u8;
     println!("{}", number2);
 }

This prints out 246. Note that we use the i32 at the end of -10 to put the type there. This is just another way of doing it; you may like it or not like it but it is an option it is nice to be aware of. The reason for printing out 246 is a u8 can only store numbers from 0 to 255. The 10th number from 256 is 246. However, it is extremely rare you will want to rely on this behaviour so instead we would do this instead:

 fn main() {
     let number1 = 10i32;
     let number2: u8 = number1.try_into().unwrap();
     println!("{}", number2);
 }

Now we have to use explicit typing on number2 to show that we want to convert it to a u8. The try_into() function tries to convert it to a number and returns a type called 'Result' that indicates whether the conversion was a success. The unwrap() means that we are going to assume the conversion was a success. If the conversion wasn't a success i.e. the other number isn't representable with the new number type, then the program will crash, which is called panicking in Rust. It may seem odd to crash over a number not being converted correctly, but this is an important concept. If something goes wrong, what do we want to do? We have two options: abort and stop running, or continue and try to run with what we have. However, small errors can cause unseen consequences. Often programs rely on values or numbers being as we expect. If we use 'as' for our conversions, we could easily get strange unintended results which cause bugs much further down the program, which are very hard to track down. Instead we often want to just crash and be able to determine that there is a bug and where it occured, allowing us to fix the bug. Only in extremely rare circumstances do we want to rely on the strange behaviour of 'as' when the numbers don't fit.

Going back to the question from the last chapter, we wanted to return a floating point from our get_median() function. We can return an f64 from the function and since an f64 is larger than an i32, we can use the safe conversion method.

 fn get_median(input: Vec<i32>) -> f64 {
     let mut array = input;
     //sort array
     for index1 in 0..array.len() {
         for index2 in index1..array.len() {
             if array[index2] > array[index1] {
                 array.swap(index1, index2);
             }
         }
     }
     //return median
     if array.len() % 2 == 0 {
         //even
         let middle_value1 = array[array.len() / 2 - 1].into();
         let middle_value2 = array[array.len() / 2].into();
         let median = (middle_value1 + middle_value2) / 2.0;
         return median;
     } else {
         //odd
         let median = array[array.len() / 2].into();
         return median;
     }
 }

Note that since we are returning an f64 from the function, the compiler can work out when we use .into() we want an f64 out, so we don't have to write out the type. Now this function works as expected, returning the correct median values.

Next: Tuples and Structs