# Converting Temperatures in Rust

Next up is my third program written in Rust. All the prompt said was:

Convert temperatures between Fahrenheit and Celsius.

This could be simple functions to go back and forth between Celsius and Fahrenheit. But I wanted to try and challenge myself to do handle some user input and use some of Rust's more advanced features.

## Enums

Last week I went over the power `match` expressions can provide, and this week we're going to go even further with Enums. Enums allow you to create a custom type that "enumerates" all the possible values.

Since we are dealing with temperatures, I thought it would be fitting to capture the two scales we use with an Enum.

``````enum Temp {
F(f64),
C(f64),
}
``````

We use the keyword `enum` and then define all the possible versions our type can handle. In this case, it is `F` for Fahrenheit, and `C` for Celsius. And each one can hold a floating point number for the degrees.

With this, we can now use a `match` expression to do the actual calculations for conversions:

``````fn convert_temp(temp: &Temp) -> f64 {
match temp {
&Temp::F(degrees) => (degrees - 32.0) / 1.8,
&Temp::C(degrees) => (degrees * 1.8) + 32.0,
}
}
``````

This should look like what we've seen before. This function gets passed a reference to a Temp type. The `&` signifies it's a reference. This function doesn't need "ownership" of the Temp since we are returning a new float based on it, not mutating it

We use pattern-matching to do the correct conversion to the other scale, and return a new float.

## Let's print some numbers!

Now to use this conversion! Once again, the `match` expression is our friend so we can show the temperature in both scales:

``````fn print_temp(temp: &Temp) {
match temp {
&Temp::F(degrees) => println!("{}F = {}C", degrees, convert_temp(temp)),
&Temp::C(degrees) => println!("{}C = {}F", degrees, convert_temp(temp)),
}
}
``````

`print_temp` also takes a reference to a Temp. It uses `match` to decide which format it needs, and then prints out both the submitted and converted degrees.

Let's test it out with some sample data to make sure it looks correct. Here I've generated a list of temperatures wrapped in the appropriate Temp enum:

``````fn sample_temps() {
println!("Sample conversions:");

let temps = [
Temp::F(-40.0), // -40
Temp::F(0.0),   // -18
Temp::F(32.0),  // 0
Temp::F(60.0),  // 16
Temp::F(100.0), // 38
Temp::F(150.0), // 66
Temp::F(212.0), // 100
Temp::C(-40.0), // -40
Temp::C(0.0),   // 32
Temp::C(15.0),  // 59
Temp::C(30.0),  // 86
Temp::C(60.0),  // 140
Temp::C(100.0), // 212
Temp::C(200.0), // 392
];

for temp in temps.iter() {
print_temp(temp);
}
}

fn main() {
println!("Welcome to temperature converter!\n");

sample_temps();
}
``````

I put in comments what the correct output should be. We then use a `for...in` loop to iterate over each of these and output the conversion. Finally, we add in some headings and call this in `main()`. And out comes all the correct conversions!

``````Welcome to temperature converter!

Sample conversions:
-40F = -40C
0F = -17.77777777777778C
32F = 0C
60F = 15.555555555555555C
100F = 37.77777777777778C
150F = 65.55555555555556C
212F = 100C
-40C = -40F
0C = 32F
15C = 59F
30C = 86F
60C = 140F
100C = 212F
200C = 392F
``````

## User Input

This is great, but unless we want users adding new temperatures to that list, it's not a very useful program. So let's allow for user to add in their own input and call it in `main` after our sample data:

``````use std::io;

fn get_user_temp() {
println!("\nType \"quit\" to end the program");

loop {
let mut temp_input = String::new();

println!("\nPlease input a temperature you want to convert (Format: 100F or -40C):");

io::stdin()

let trimmed = temp_input.trim();

if trimmed == "quit" {
break;
}

let (temp, scale) = trimmed.split_at(trimmed.len() - 1);

let temp: f64 = match temp.parse() {
Ok(num) => num,
Err(_) => continue,
};

let temp: Temp = match scale {
"C" => Temp::C(temp),
"F" => Temp::F(temp),
_ => continue,
};

print_temp(&temp);
}
}
``````

We start by telling the user how to quit the program. We're going to leave this thing running for as long as they want to keep converting.

We then enter into a `loop` that will keep running until we call `break`.

We then create a mutable String variable called `temp_input` to store the user input.

Next, we explain what format their queries should be in. We support a number immediately followed by either `F` or `C`. This is to make our lives easier for parsing later.

We use the standard library's IO function to read a line from the terminal, which saves it into `temp_input`. We then `trim()` what the user typed in to remove any extra characters and put in our check for if they wanted to quit. If they did, we break out of the loop and the program ends.

We split the string at the last character to see if the temperature is in C or F. We use `(temp, scale)` to destructure the result of `split_at` into variables we can use later.

Next, we try and parse the first part of the string into a float. If they typed a number, we store it into `temp`. Otherwise we call `continue`, which starts the loop over and gives the user the instructions so they can try again.

We then `match` on the last character of their input. If it was a "C", then we convert `temp` into a `Temp::C`. If it's an "F" we convert to a `Temp::F`. If they put anything else in, we also `continue` and let them try again.

If everything is good at this point, we have a well-formed `Temp`, and we print out the conversion:

``````Type "quit" to end the program

Please input a temperature you want to convert (Format: 100F or -40C):
> 100.5F
100.5F = 38.05555555555556C
``````

## Conclusion

In the end, your `main.rs` should look something like this:

``````use std::io;

enum Temp {
F(f64),
C(f64),
}

fn convert_temp(temp: &Temp) -> f64 {
match temp {
&Temp::F(degrees) => (degrees - 32.0) / 1.8,
&Temp::C(degrees) => (degrees * 1.8) + 32.0,
}
}

fn print_temp(temp: &Temp) {
match temp {
&Temp::F(degrees) => println!("{}F = {}C", degrees, convert_temp(temp)),
&Temp::C(degrees) => println!("{}C = {}F", degrees, convert_temp(temp)),
}
}

fn sample_temps() {
println!("Sample conversions:");

let temps = [
Temp::F(-40.0), // -40
Temp::F(0.0),   // -18
Temp::F(32.0),  // 0
Temp::F(60.0),  // 16
Temp::F(100.0), // 38
Temp::F(150.0), // 66
Temp::F(212.0), // 100
Temp::C(-40.0), // -40
Temp::C(0.0),   // 32
Temp::C(15.0),  // 59
Temp::C(30.0),  // 86
Temp::C(60.0),  // 140
Temp::C(100.0), // 212
Temp::C(200.0), // 392
];

for temp in temps.iter() {
print_temp(temp);
}
}

fn get_user_temp() {
println!("\nType \"quit\" to end the program");

loop {
let mut temp_input = String::new();

println!("\nPlease input a temperature you want to convert (Format: 100F or -40C):");

io::stdin()

let trimmed = temp_input.trim();

if trimmed == "quit" {
break;
}

let (temp, scale) = trimmed.split_at(trimmed.len() - 1);

let temp: f64 = match temp.parse() {
Ok(num) => num,
Err(_) => continue,
};

let temp: Temp = match scale {
"C" => Temp::C(temp),
"F" => Temp::F(temp),
_ => continue,
};

print_temp(&temp);
}
}

fn main() {
println!("Welcome to temperature converter!\n");

sample_temps();
get_user_temp();
}

``````

For me, this was a good exploration into how Rust handles references, strings, enums, and a lot more. I hope this walk-through gave you some examples of how you can use these Rust features in your programs as well! Some of this definitely took me awhile. But thanks to helpful compiler warnings and the docs, I was able to figure it out!

What's nice is that Rust nudges you into creating programs that don't crash, even when dealing with something as fickle as user-inputted text! It was amazing to see how easy it was to create a program that guarded against incorrect input and allowed users to try again.

I'm excited to start building some more complicated programs and see where Rust guides me as I deal with different types of data!