4
\$\begingroup\$

I just wrote this decimal to Roman numeral converter, inspired by Kevlin Henney's talk "Get Kata".

I have been reading the Rust docs little by little and this is one of my first attempts at some code.

I'd like to get feedback on style, general rust idioms and how I could have implemented this algorithm in a more succinct manner.

Code also available here.

use std::env;
use std::io;
use std::io::BufWriter;
use std::io::Write;

static BASE10_NUMERALS: [&str; 7] = ["I", "X", "C", "M", "X̄", "C̄", "M̄"];

static CENTRE_NUMERALS: [&str; 6] = ["V", "L", "D", "V̄", "L̄", "D̄"];

struct Bases {
    base: usize
}


impl Iterator for Bases {
    type Item = usize;

    fn next(&mut self) -> Option<usize> {
        self.base = self.base - 1;
        Some(self.base)
    }
}

fn iter_bases(largest_base: usize) -> Bases {
    Bases { base: largest_base }
}


fn encode((decimal_number, base, ): (char, usize, )) -> String {
    let digit = decimal_number.to_digit(10).unwrap();
    let max_base = CENTRE_NUMERALS.len();

    if base >= max_base {
        BASE10_NUMERALS[BASE10_NUMERALS.len() - 1]
            //This pow function is the main limiter for decimal size
            .repeat((10_u32.pow((base - max_base) as u32) * digit) as usize)
    } else {
        if digit == 9 {
            format!("{}{}"
                    , if base == 3 { "Ī"} else { BASE10_NUMERALS[base] }
                    , BASE10_NUMERALS[base + 1]
            )
        } else if digit >= 5 {
            format!("{}{}", CENTRE_NUMERALS[
                base], BASE10_NUMERALS[base]
                        .repeat((digit - 5) as usize))
        } else if digit == 4 {
            format!("{}{}", BASE10_NUMERALS[base], CENTRE_NUMERALS[base])
        } else {
            // Less than 4
            BASE10_NUMERALS[base].repeat(digit as usize)
        }
    }
}


fn main() {
    let args: Vec<String> = env::args().collect();
    let mut writer = BufWriter::new(io::stdout());
    let input = &args[1];
    for roman_numeral in input
        .chars()
        .zip(iter_bases(input.len()))
        .map(encode) {
        writer.write(roman_numeral.as_bytes())
            .expect("Unable to write to stdout");
    }
}
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

cargo fmt and cargo clippy

Use cargo fmt to format your code according to the official Rust Style Guide.

Clippy's suggestions:

  • collapse else { if .. } into else if ..;

  • change self.base = self.base - 1 to self.base -= 1;

  • use write_all instead of write when writing a slice of bytes, since the latter only makes one writing attempt:

    io::Write::write(_vectored) and io::Read::read(_vectored) are not guaranteed to process the entire buffer. They return how many bytes were processed, which might be smaller than a given buffer’s length. If you don’t need to deal with partial-write/read, use write_all/read_exact instead. (source)

The interface

Instead of passing in a tuple, it is preferable to pass separate parameters:

fn encode(decimal_number: char, base: usize) -> String {
    // ...
}

Now, we notice that the interface is fairly curious. A better self-explanatory interface might simply be:

fn to_roman(number: u32) -> String {
    // ...
}

which can be coded to encapsulate calls to encode while avoiding repetitive allocation.

Grouping use declarations

It's common to group use declarations together:

use std::{
    env,
    io::{self, BufWriter, Write},
};

The Bases utility

Bases is not necessary — you can use (0..n).rev() to iterate from n - 1 to 0.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks! All great points, I really should have known about (0..n).rev(). \$\endgroup\$ Commented Nov 22, 2020 at 8:54

Your Answer

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

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