// --- Import necessary modules ---
// std::io is needed for handling input/output operations like printing to console
// and reading user input.
// The `Write` trait specifically is needed for `flush()` to ensure prompts appear
// before the program waits for input.
use std::io::{self, Write};
// --- Define the Data Structure ---
// For a fixed set of known fields like name, age, and height, a `struct`
// is generally more appropriate and idiomatic in Rust than a `Vec`.
// - It's type-safe: each field has a defined type (String, u32, f64).
// - It's descriptive: fields have meaningful names (name, age, height_meters).
// - It clearly represents a single logical entity (a user's profile).
// A `Vec` is better suited for collections of items of the *same* type, or
// when the number of items isn't known at compile time.
// We use `#[derive(Debug)]` to allow easy printing of the whole struct for debugging.
#[derive(Debug)]
struct UserProfile {
name: String,
age: u32, // Using u32 for age, as it's typically a non-negative whole number.
height_meters: f64, // Using f64 for standard floating-point precision.
}
// --- Helper Function to Read and Validate String Input ---
// This function simplifies getting a non-empty string from the user.
// It takes the prompt message as input and returns a `Result` containing
// the valid `String` or an `io::Error` if reading fails.
fn get_non_empty_string(prompt: &str) -> io::Result<String> {
// Loop indefinitely until valid input is received.
loop {
// Print the prompt message to the console without a newline.
print!("{}", prompt);
// Ensure the prompt is displayed immediately before waiting for input.
// `flush()` returns a Result, so we use `?` to propagate potential errors.
io::stdout().flush()?;
// Create an empty mutable string to store the user's input.
let mut buffer = String::new();
// Read a line of text from standard input (the console).
// `read_line` appends to the buffer. It also returns a Result.
io::stdin().read_line(&mut buffer)?;
// Remove leading/trailing whitespace (like the newline character).
let trimmed_input = buffer.trim();
dbg!(trimmed_input);
// Check if the input is empty after trimming.
if trimmed_input.is_empty() {
// If empty, inform the user and loop again.
println!("Input cannot be empty. Please try again.");
} else {
// If not empty, convert the trimmed string slice (`&str`)
// into an owned `String` and return it wrapped in `Ok`.
// This exits the loop.
return Ok(trimmed_input.to_string());
}
}
}
// --- Helper Function to Read and Validate u32 Integer Input ---
// This function simplifies getting a valid `u32` integer from the user.
// It takes the prompt message and returns a `Result` containing the
// valid `u32` or an `io::Error`.
fn get_u32(prompt: &str) -> io::Result<u32> {
// Loop until valid input is received.
loop {
// Print prompt and flush output buffer.
print!("{}", prompt);
io::stdout().flush()?;
// Read the line from the user.
let mut buffer = String::new();
io::stdin().read_line(&mut buffer)?;
// Trim whitespace and attempt to parse the input as a u32 integer.
// `parse::<u32>()` returns a `Result<u32, ParseIntError>`.
match buffer.trim().parse::<u32>() {
// If parsing is successful (`Ok`), return the value wrapped in `Ok`.
Ok(value) => return Ok(value),
// If parsing fails (`Err`), inform the user and the loop continues.
Err(_) => {
println!("Invalid input. Please enter a positive whole number (e.g., 30).");
}
}
}
}
// --- Helper Function to Read and Validate f64 Float Input ---
// This function simplifies getting a valid `f64` float from the user.
// It takes the prompt message and returns a `Result` containing the
// valid `f64` or an `io::Error`.
fn get_f64(prompt: &str) -> io::Result<f64> {
// Loop until valid input is received.
loop {
// Print prompt and flush output buffer.
print!("{}", prompt);
io::stdout().flush()?;
// Read the line from the user.
let mut buffer = String::new();
io::stdin().read_line(&mut buffer)?;
// Trim whitespace and attempt to parse the input as an f64 float.
// `parse::<f64>()` returns a `Result<f64, ParseFloatError>`.
match buffer.trim().parse::<f64>() {
// If parsing is successful (`Ok`), return the value wrapped in `Ok`.
Ok(value) => return Ok(value),
// If parsing fails (`Err`), inform the user and the loop continues.
Err(_) => {
println!("Invalid input. Please enter a number (e.g., 1.75).");
}
}
}
}
// --- Main Function ---
// The entry point of the program.
// It returns `io::Result<()>` which allows using the `?` operator
// for concise error handling within the function for I/O operations.
fn main() -> io::Result<()> {
println!("--- Please Enter Your Details ---");
// Call the helper function to get the name. The `?` operator will return
// early with the error if `get_non_empty_string` fails.
let name = get_non_empty_string("1. Enter your Name: ")?;
// Call the helper function to get the age.
let age = get_u32("2. Enter your Age: ")?;
// Call the helper function to get the height.
let height = get_f64("3. Enter your Height (in meters, e.g., 1.75): ")?;
// Create an instance of the UserProfile struct using the collected data.
// We can use the field init shorthand if the variable name matches the field name.
let user_profile = UserProfile {
name, // Shorthand for name: name
age, // Shorthand for age: age
height_meters: height, // Variable name `height` doesn't match field `height_meters`
};
// --- Print the Collected Data ---
println!("\n--- Thank you! Data Collected: ---");
// Method 1: Using the Debug trait (`{:?}`) for a quick struct representation.
// Useful for developers during debugging.
println!("Debug Output: {:?}", user_profile);
// Method 2: Printing fields individually for formatted user output.
println!("\nFormatted Output:");
println!(" Name: {}", user_profile.name);
println!(" Age: {} years old", user_profile.age);
// Use {:.2} in the format string to display the float with 2 decimal places.
println!(" Height: {:.2} meters", user_profile.height_meters);
// If all operations were successful, return `Ok(())`.
// `()` is the unit type, indicating success with no specific value to return.
Ok(())
}