I'm new to rust, and this is my very first rust program.
I'd like to write a simple CLI app to help me running some Git command on multiple repositories. Since my goal is learning rust, I force myself not to use any dependency so that I have to implement the tools I need. I started writing a very small API for parsing command line arguments. My code builds and seems to work as expected on simple cases. I'd like to have some advice on those few lines: what's bad in this code, what should I change right now? The things I've struggled (just to make the code build):
Lifetime It looks like that when doing data structure composition, I have to propagate lifetime nearly everywhere as soon as I use references to avoid unnecessary copy. Am I right to worry about this, or should I just get use to this when coding with rust? Could it be better/simpler than what I did?
borrow checker In 'command_line_parse' I have two vectors as parameter.
- The first one is simply the command line arguments to analyse.
- The second one is a list defining each command (with argument) and options I'd like to handle.
The function return a new vector with references to the commands and options definition found in command line. A command may also have a parameter (next to the command) ; but if the next element after a command have been identified as another command or option, the command has no parameter.
In the last for of commandline.rs, I had to build and use a temporary vector 'index_match': Since I already have a mutable reference to iterate vector 'result', I wasn't able to search directly in 'result' for 'index_command_param' in a nested loop because of the borrow checker. Could I avoid building 'index_match' temporary vector to do this ?
main.rs
mod commandline; use crate::commandline::*; use std::env; fn main() { let syntax: Vec<ArgumentDefinition> = vec![ ArgumentDefinition { short: Some("-l"), long: Some("--list"), kind: ArgumentKind::Option, expect: None, description: "list repositories.", }, ArgumentDefinition { short: Some("-p"), long: Some("--pull"), kind: ArgumentKind::Option, expect: None, description: "pull repositories branch.", }, ArgumentDefinition { short: Some("-s"), long: Some("--switch"), kind: ArgumentKind::Command, expect: Some("branch"), description: "switch repositories branch.", }, ]; let arguments: Vec<String> = env::args().collect(); let result = command_line_parse(&arguments, &syntax); dbg!(result); }
commandline.rs
pub type ArgumentIndex = usize; #[derive(Debug)] #[allow(dead_code)] pub enum ArgumentKind { Command, Option, } #[derive(Debug)] pub struct ArgumentDefinition<'a> { pub short: Option<&'a str>, pub long: Option<&'a str>, pub kind: ArgumentKind, pub expect: Option<&'a str>, pub description: &'a str, } #[derive(Debug)] pub struct ArgumentValue<'a> { pub index: ArgumentIndex, pub parameter: Option<&'a str>, } #[derive(Debug)] pub struct ArgumentParseResult<'a> { pub result: ArgumentValue<'a>, pub argument: &'a ArgumentDefinition<'a>, } fn option_seek( arguments: &Vec<String>, pattern_short: Option<&str>, pattern_long: Option<&str>, ) -> Option<usize> { let closure_argument_search = |arguments: &Vec<String>, patern: Option<&str>| -> Option<usize> { if let Some(patern_value) = patern { const SHIFT: usize = 1; for (current_index, current_value) in arguments[SHIFT..].iter().enumerate() { if current_value.eq(patern_value) { return Some(current_index + SHIFT); } } } None }; if let Some(index) = closure_argument_search(&arguments, pattern_short) { return Some(index); } if let Some(index) = closure_argument_search(&arguments, pattern_long) { return Some(index); } None } pub fn command_line_parse<'a>( arguments: &'a Vec<String>, definition: &'a Vec<ArgumentDefinition>, ) -> Vec<ArgumentParseResult<'a>> { let mut result: Vec<ArgumentParseResult> = Vec::new(); let mut index_match: Vec<ArgumentIndex> = Vec::new(); for argument_definition in definition { if let Some(index) = option_seek( arguments, argument_definition.short, argument_definition.long, ) { index_match.push(index); let argument_value = ArgumentValue { index: index, parameter: None, }; let argument_parse_result = ArgumentParseResult { result: argument_value, argument: &*argument_definition, }; result.push(argument_parse_result); } } for result_element in &mut result { match result_element.argument.kind { ArgumentKind::Command => { let index_command_param = result_element.result.index + 1; if index_match .iter() .position(|&r| r == index_command_param) .is_none() { result_element.result.parameter = Some(&arguments[index_command_param]) } } ArgumentKind::Option => {} } } return result; }