Skip to main content
2 of 2
Code fence
Toby Speight
  • 81.7k
  • 14
  • 101
  • 308

Rust: Command line menu in ASCII table

Specific areas in which I'd love to get feedback:

  • Is it good to have the Command struct own the Strings (description field)? Or since these strings aren't really meant to change, should I rather have them as &'static strs?
    • I initially used String but then switched to &'static str, and then I saw this SO post and switched back to String.
  • Within the print_table function, am I complicating the code by having the max_lengths (for two fields of the Command struct) as a tuple instead of using two separate scalar variables?
  • Also, is there a more idiomatic way of finding the maximum of lengths of a String field in array of structs?
  • I've seen imports being placed at the top (e.g., use std::cmp::max) before the functions are used (max(1, 2)). I know that this keeps the code terse. However, I'm also a fan of qualified naming (e.g., in Python, it's encouraged to do import math; math.pow(x, y) instead of from math import pow; pow(x, y)). This way, to readers, there's no confusion about the source of a function. What's the general consensus of Rust community? Up to each dev?
fn main() {
    let commands: [Command; 3] = [
        Command {code: '1', description: String::from("Start your instance")},
        Command {code: '2', description: String::from("Stop your instance")},
        Command {code: 'q', description: String::from("Quit")},
    ];
    
    print_table(&commands);
    
    for cmd in commands.iter() {
        match cmd.code {
            '1' => println!("Starting instance"),
            '2' => println!("Stopping instance"),
            'q' => println!("Quitting"),
            _ => println!("Invalid option"),
        }
    }
}

fn print_table(commands: &[Command; 3]) {
    const BUFFER: usize = 2;
    const HEADING: (&str, &str) = ("Code", "Description");
    const VERTICAL_BAR: &str = "│";
    const HORIZONTAL_SEP: &str = "─";
    
    std::process::Command::new("clear").status().unwrap();
    
    let mut max_lengths: (usize, usize) = (0, 0);
    for cmd in commands.iter() {
        max_lengths.0 = std::cmp::max(max_lengths.0, cmd.code.len_utf8());
        max_lengths.1 = std::cmp::max(max_lengths.1, cmd.description.len());
    }
    max_lengths.0 = std::cmp::max(max_lengths.0, HEADING.0.len());
    max_lengths.1 = std::cmp::max(max_lengths.1, HEADING.1.len());
    
    let horizontal_lines: (&str, &str) = (
        &HORIZONTAL_SEP.repeat(max_lengths.0 + BUFFER),
        &HORIZONTAL_SEP.repeat(max_lengths.1 + BUFFER),
    );
    
    println!("╭{}┬{}╮", horizontal_lines.0, horizontal_lines.1);
    println!("{VERTICAL_BAR}{:^w1$}{VERTICAL_BAR} {:^w2$}{VERTICAL_BAR}", HEADING.0, HEADING.1, w1=max_lengths.0+BUFFER, w2=max_lengths.1+BUFFER-1);
    println!("├{}┼{}┤", horizontal_lines.0, horizontal_lines.1);
    for cmd in commands.iter() {
        println!("{VERTICAL_BAR}{:^w1$}{VERTICAL_BAR} {:<w2$}{VERTICAL_BAR}", cmd.code, cmd.description, w1=max_lengths.0+BUFFER, w2=max_lengths.1+BUFFER-1);
    }
    println!("╰{}┴{}╯", horizontal_lines.0, horizontal_lines.1);
}

struct Command {
    code: char,
    description: String,
}