This is my usual workflow for Rust projects:
- I write a big chunk of code.
- I split that code into functions.
- I turn those functions intro struct methods (to add structure).
The following (mock) code is step 2 of my workflow. Brief explanation: If the user presses a key that matches the key
field in a Keymap
, run the command that belongs to that Keymap
. The command can have a prompt
, and ask for the user for input, but it's optional.
use std::io::Write;
struct Keymap<'a> {
key: char,
command: &'a str,
prompt: Option<&'a str>,
}
fn show_cursor(mut stdout: impl Write) {
write!(stdout, "{}", "The cursor is visible now!").unwrap();
}
fn show_prompt(prompt: &str, mut stdout: impl Write) {
write!(stdout, "{}", prompt).unwrap();
stdout.flush().unwrap();
}
// In the actual application, this turns pressed keys into text input
fn get_input(stdin: &str, mut stdout: impl Write) -> String {
write!(stdout, "{}", stdin).unwrap();
stdin.to_owned()
}
fn handle_input(key: char, keymaps: &[Keymap], stdin: &str, mut stdout: impl Write) {
if let Some(keymap) = keymaps.iter().find(|k| k.key == key) {
match keymap.prompt {
Some(_) => {
show_prompt(keymap.prompt.unwrap(), &mut stdout);
let input = get_input(stdin, &mut stdout);
println!("{} executed with input: {}", keymap.command, input);
}
None => {
println!("{} executed without input", keymap.command);
}
}
}
}
fn main() {
let mut stdout = Vec::new();
stdout.flush().unwrap();
let keymaps = vec![Keymap {
key: 't',
// in the actual application, `{}` is replaced by the input
command: "echo {}",
prompt: Some("Enter your input:"),
}];
show_cursor(&mut stdout);
// Here I hardcoded the `t` character. In the real application, the user presses it.
handle_input('t', &keymaps, "Pressed keys", stdout);
}
All those functions work with stdout
, so I figured I could put them in a struct called Screen
(step 3 of my workflow):
use std::io::Write;
struct Keymap<'a> {
key: char,
command: &'a str,
prompt: Option<&'a str>,
}
struct Screen {
stdout: Vec<u8>,
}
impl Screen {
fn new(stdout: Vec<u8>) -> Self {
Screen { stdout }
}
fn show_cursor(&mut self) {
write!(self.stdout, "{}", "The cursor is visible now!").unwrap();
}
fn show_prompt(&mut self, prompt: &str) {
write!(self.stdout, "{}", prompt).unwrap();
self.stdout.flush().unwrap();
}
// In the actual application, this turns pressed keys into text input
fn get_input(&mut self, stdin: &str) -> String {
write!(self.stdout, "{}", stdin).unwrap();
stdin.to_owned()
}
fn handle_input(&mut self, key: char, keymaps: &[Keymap], stdin: &str) {
if let Some(keymap) = keymaps.iter().find(|k| k.key == key) {
match keymap.prompt {
Some(_) => {
self.show_prompt(keymap.prompt.unwrap());
let input = self.get_input(stdin);
println!("{} executed with input: {}", keymap.command, input);
}
None => {
println!("{} executed without input", keymap.command);
}
}
}
}
}
fn main() {
let stdout = Vec::new();
let mut screen = Screen::new(stdout);
screen.stdout.flush().unwrap();
let keymaps = vec![Keymap {
key: 't',
// in the actual application, `{}` is replaced by the input
command: "echo {}",
prompt: Some("Enter your input:"),
}];
screen.show_cursor();
// Here I hardcoded the `t` character. In the real application, the user presses it.
screen.handle_input('t', &keymaps, "Pressed keys");
}
Now, these are the problems I usually have:
- Sometimes I'm not sure which variables should be fields of the struct. In this case,
stdin
and&[Keymap]
. - Sometimes I'm not sure if a function belongs to that struct. For example,
handle_input
isn't modifyingstdout
. Should it be a method ofScreen
? - Sometimes I'm not sure if I should have created the struct at all.
I'd appreciate some advice on the matter.