1
\$\begingroup\$

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 Fₙ:");
        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 => '₀',
                    1 => '₁',
                    2 => '₂',
                    3 => '₃',
                    4 => '₄',
                    5 => '₅',
                    6 => '₆',
                    7 => '₇',
                    8 => '₈',
                    9 => '₉',
                    10.. => unreachable!(),
                };
                n /= 10;
                res = format!("{res}{digit}");
            }
            res
        }
    }
}

Comments?

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$
  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.

  2. break match is strange. I would rather use:

match n.trim().parse() {
    Ok(n) => break n,
    Err(_) => continue,
}
  1. 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' => '₀',
            '1' => '₁',
            '2' => '₂',
            '3' => '₃',
            '4' => '₄',
            '5' => '₅',
            '6' => '₆',
            '7' => '₇',
            '8' => '₈',
            '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 ("₀", "₁") instead of chars then use push_str() - this may be a little more efficient as it doesn't need to determine the character length.

  1. Your subscript() function prints the subscript backwards (my version does it correctly) - you can fix that by changing format!("{res}{digit}") to format!("{digit}{res}") or insert(0, digit) instead of push().

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 Fₙ:");
            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' => '₀',
            '1' => '₁',
            '2' => '₂',
            '3' => '₃',
            '4' => '₄',
            '5' => '₅',
            '6' => '₆',
            '7' => '₇',
            '8' => '₈',
            '9' => '₉',
            _ => unreachable!(),
        })
        .collect()
}
```
\$\endgroup\$
1
  • \$\begingroup\$ No idea how could I miss this bug about characters being printed in the wrong order :) \$\endgroup\$ Dec 30, 2022 at 1:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.