# Rust decimal to roman numeral kata

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 { "Ī"} 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");
}
}


# 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.

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