This is how I implemented the guessing game - chapter 2 of the Rust book.
use std::io;
use std::io::Error;
use std::cmp::Ordering;
use std::num::ParseIntError;
use rand::Rng;
enum MyReadLineError {
FailReadLine(Error)
}
#[derive(Debug)]
enum MyReadU32Line {
FailReadLine(Error),
FailParse(ParseIntError)
}
fn my_read_line() -> Result<String, MyReadLineError> {
let mut input = String::new();
let result = io::stdin().read_line(&mut input);
match result {
Ok(_) => Ok(input),
Err(error ) => Err(MyReadLineError::FailReadLine(error)),
}
}
fn my_read_u32_line() -> Result<u32, MyReadU32Line> {
match my_read_line() {
Result::Ok(line) =>
match line.trim().parse::<u32>() {
Ok(value) => Ok(value),
Err(error) => Err(MyReadU32Line::FailParse(error)),
}
Result::Err(MyReadLineError::FailReadLine(error)) => Err(MyReadU32Line::FailReadLine(error)),
}
}
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1, 101);
loop {
println!("Please input your guess.");
let guess = my_read_u32_line();
match guess {
Ok (value) => match value.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
},
}
Err (_) => println!("Sorry I couldn't read a u32 from your input! Please try again."),
}
}
}
This works as expected but I would like to flatten the matches so that doesn't look at verbose, is that possible. Also is there any other way to make that code look better, more readable?
1 Answer 1
Welcome to Rust!
Code Formatting
There are some inconsistencies in your formatting — I have run
cargo fmt
to get rid of them.
Naming
There's no need to prefix everything with My
or my_
— in
Rust, your names won't as easily clash with existing names as might be
the case in certain other languages.
Error handling
For applications, I suggest using the anyhow
crate to manage
errors — it reduces much of the boilerplate. Just add
anyhow = "1.0"
to the [dependencies]
section of your Cargo.toml
.
Helper functions
The my_read_line
function can be simplified with the ?
operator:
use anyhow::Result;
fn read_line() -> Result<String> {
let mut input = String::new();
io::stdin().read_line(&mut input)?;
Ok(input)
}
my_read_u32_line
can be similarly simplified:
fn read_u32() -> Result<u32> {
Ok(read_line()?.trim().parse()?)
}
Magic numbers
We can define constants for the range of the number:
const MIN: u32 = 1;
const MAX: u32 = 100;
// later
let secret_number = rand::thread_rng().gen_range(MIN, MAX + 1);
Organization
I might reorganize the main
function to reduce indentation:
loop {
println!("Please input your guess.");
let guess = match read_u32() {
Ok(guess) => guess,
Err(_) => {
println!(
"Sorry I couldn't read a u32 from your input! \
Please try again."
);
continue;
}
};
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
};
}
Final result
use anyhow::Result;
use rand::Rng;
use std::cmp::Ordering;
use std::io;
const MIN: u32 = 1;
const MAX: u32 = 100;
fn read_line() -> Result<String> {
let mut input = String::new();
io::stdin().read_line(&mut input)?;
Ok(input)
}
fn read_u32() -> Result<u32> {
Ok(read_line()?.trim().parse()?)
}
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(MIN, MAX + 1);
loop {
println!("Please input your guess.");
let guess = match read_u32() {
Ok(guess) => guess,
Err(_) => {
println!(
"Sorry I couldn't read a u32 from your input! \
Please try again."
);
continue;
}
};
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
};
}
}
-
\$\begingroup\$ Thanks for your answer, this is really awesome!!! Real nice of you =] \$\endgroup\$Natalie Perret– Natalie Perret2020年09月24日 18:44:26 +00:00Commented Sep 24, 2020 at 18:44