I'm trying to learn Rust and for this reason I'm reading the Rust Book. It contains the following exercise:
Generate the nth Fibonacci number.
Here's my take. Note I'm only using material from chapters 1-3 of this book (since I've only read so far yet), with the exception of format!
which I learned by Googling it and unreachable!
, which I learned by asking other people. I think I needed both facilities to complete the task.
use std::io;
fn main() {
let n = loop {
println!("Enter n to get Fn:");
let mut n = String::new();
io::stdin()
.read_line(&mut n)
.expect("Failed to read line");
break match n.trim().parse() {
Ok(n) => n,
Err(_) => continue,
}
};
let subscript_n = subscript(n);
let res = fib(n);
println!("F{subscript_n} = {res}");
}
fn fib(n: u32) -> u64 {
match n {
0 => 0,
n => {
let mut fib_i = 1u64;
let mut fib_i_minus_1 = 0u64;
for _ in 1..n {
(fib_i, fib_i_minus_1) = (fib_i.checked_add(fib_i_minus_1).expect("Result too large"), fib_i);
}
fib_i
}
}
}
fn subscript(n: u32) -> String {
match n {
0 => format!("0"),
mut n => {
let mut res = String::new();
while n != 0 {
let digit = match n % 10 {
0 => '0',
1 => '1',
2 => '2',
3 => '3',
4 => '4',
5 => '5',
6 => '6',
7 => '7',
8 => '8',
9 => '9',
10.. => unreachable!(),
};
n /= 10;
res = format!("{res}{digit}");
}
res
}
}
}
Comments?
1 Answer 1
I would create the input string outside the loop and
clear()
it before each iteration, as this is more performant (no need to allocate twice). This code isn't performance sensitive, but still a good habit.break match
is strange. I would rather use:
match n.trim().parse() {
Ok(n) => break n,
Err(_) => continue,
}
- I would rewrite
subscript()
as follows. This is subjective but I find it more readable (although perhaps less performant):
fn subscript(n: u32) -> String {
n.to_string()
.chars()
.map(|c| match c {
'0' => '0',
'1' => '1',
'2' => '2',
'3' => '3',
'4' => '4',
'5' => '5',
'6' => '6',
'7' => '7',
'8' => '8',
'9' => '9',
_ => unreachable!(),
})
.collect()
}
If you insist on using the old version, then format!("0")
is rather unusual way to convert a string literal to a String
- the commonly used ways are String::from("0")
, "0".into()
(where the compiler can infer the target type is String
), "0".to_string()
and "0".to_owned()
. I personally prefer to_owned()
as I think it reflects the intent the best - you want to go from a borrowed &str
to an owned String
. For pushing a char into a String
we got String::push()
- format!("{res}{digit}")
is both unidiomatic and inefficient (as it creates a copy and use the formatting machinery which is pretty heavy). You can also use string literals ("0"
, "1"
) instead of char
s then use push_str()
- this may be a little more efficient as it doesn't need to determine the character length.
- Your
subscript()
function prints the subscript backwards (my version does it correctly) - you can fix that by changingformat!("{res}{digit}")
toformat!("{digit}{res}")
orinsert(0, digit)
instead ofpush()
.
The code after all changes:
use std::io;
fn main() {
let n = {
let mut n = String::new();
loop {
n.clear();
println!("Enter n to get Fn:");
io::stdin().read_line(&mut n).expect("Failed to read line");
match n.trim().parse() {
Ok(n) => break n,
Err(_) => continue,
}
}
};
let subscript_n = subscript(n);
let res = fib(n);
println!("F{subscript_n} = {res}");
}
fn fib(n: u32) -> u64 {
match n {
0 => 0,
n => {
let mut fib_i = 1u64;
let mut fib_i_minus_1 = 0u64;
for _ in 1..n {
(fib_i, fib_i_minus_1) = (
fib_i.checked_add(fib_i_minus_1).expect("Result too large"),
fib_i,
);
}
fib_i
}
}
}
fn subscript(n: u32) -> String {
n.to_string()
.chars()
.map(|c| match c {
'0' => '0',
'1' => '1',
'2' => '2',
'3' => '3',
'4' => '4',
'5' => '5',
'6' => '6',
'7' => '7',
'8' => '8',
'9' => '9',
_ => unreachable!(),
})
.collect()
}
```
-
\$\begingroup\$ No idea how could I miss this bug about characters being printed in the wrong order :) \$\endgroup\$DisplayName– DisplayName2022年12月30日 01:24:12 +00:00Commented Dec 30, 2022 at 1:24