-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
13: Practice Exercise: Building a Simple Temperature Converter
How do I actually get a number from the user?
Getting input in Rust is a bit more verbose than in Python or JavaScript, and it's where most people get tripped up during their first few CLI tools. You can't just call a simple input() function. You need to bring std::io into play.
The trick is that std::io::stdin().read_line(&mut string) gives you a String, but for a temperature converter, you need a float. You'll have to trim the trailing newline character (because hitting 'Enter' adds one) and then parse it. It looks something like this:
use std::io;
let mut input = String::new();
io::stdin().read_line(&mut input).expect("Failed to read line");
// Trim the whitespace/newline and parse to f64
let temp: f64 = input.trim().parse().expect("Please type a valid number!");
I've used expect() here to keep it simple, but in a production app, you'd probably want to loop until the user gives you a valid number instead of just crashing the program.
Should I just put all the math in the main function?
You could, but you shouldn't. Even for a tiny project like this, I always recommend splitting your logic from your I/O. If you keep your conversion formulas in their own functions, you can test them independently without having to manually type numbers into the console every single time you change a line of code.
I usually write my converters as pure functions that take a float and return a float. It makes the main function read like a story rather than a math textbook:
fn celsius_to_fahrenheit(c: f64) -> f64 {
(c * 9.0 / 5.0) + 32.0
}
fn main() {
let c = 25.0;
let f = celsius_to_fahrenheit(c);
println!("{}°C is {}°F", c, f);
}
What's the best way to handle someone typing "warm" instead of "25"?
This is where Rust's Result type really shines. As I mentioned earlier, parse() doesn't return a number; it returns a Result<f64, ParseFloatError>. If you use expect(), the program panics and dies. Not a great user experience.
Instead, I like to wrap the input logic in a loop and use a match statement. This allows the program to say "Hey, that's not a number, try again" without exiting. Here is the pattern I typically use for CLI tools:
let temp: f64 = loop {
let mut input = String::new();
io::stdin().read_line(&mut input).expect("Failed to read line");
match input.trim().parse() {
Ok(num) => break num,
Err(_) => println!("That wasn't a number. Please try again!"),
}
};
By using break num, we effectively "escape" the loop only once we have a valid value to assign to our variable.
📋 Practical Task
Exercise: Build a Tri-Unit Temperature Command Line Tool
Your task is to build a complete temperature converter that supports three scales: Celsius, Fahrenheit, and Kelvin. Instead of just one conversion, create a program that asks the user for the current temperature value and the unit it is currently in, then lets them choose which unit to convert it to.
- Input Handling: Use a loop to ensure the program doesn't crash if the user enters a non-numeric value for the temperature.
- Logic: Implement separate functions for each conversion path (e.g.,
kelvin_to_celsius,fahrenheit_to_kelvin, etc.). - User Interface: Provide a simple menu (e.g., "Press 1 for C, 2 for F, 3 for K") to determine the units.
- Precision: Use
f64for all calculations to ensure accuracy.
Bonus challenge: Add a check to ensure the temperature entered isn't below Absolute Zero (-273.15°C) and warn the user if it is.
There are no comments for now.