Initiative Tracker: First Full (Practice) Project

Hello! I come from a C background, and have been learning Rust for about six weeks. This program was written when I was at chapter 13 of The Rust Programming Language.

It's a CLI initiative tracker for table-top RPGs. I'm sure there can be many improvements, so if anyone is willing, feedback and criticism would be most welcome.

I'm not confident that I fully grasp the right way to lay out modules, so any comments on this would also be appreciated.

(This is also my first time posting on github, so let me know if I didn't do it right.)

Thank you!

3 Likes

I didn't have time to look at it in a lot of detail, but here are a few initial thoughts:

  • Instead of having a sort command, I would have the tracker sort the list when it starts, so that the user has one less thing to remember.
  • You might want to keep track of the entities in a BTreeMap<String, Entity> instead of a Vec, which will make it easier to deduplicate names, as well as look entities up by name.
  • The implementation of each command is spread across several files; think about how you might restructure things so that everything related to a command is in the same place in the source code. This probably means defining something like a trait Command to be a common interface.
  • Consider returning Result<(), String> instead of bool from operations that can fail, like adding an entity. That would then let you use the ? operator to help with error handling; for example:
    fn execute_add0(c: &Command, entity_list: &mut EntityList) -> Result<String, String> {
        let name = args[0];
        entity_list.add(name.clone(), true)?;
        Ok(format!("entity {name:?} added to list")
    }
    
  • It will be much easier to write tests if you separate the terminal I/O from the rest of the logic. In particular, I'd replace Command::get with something like fn parse(String)->Result<Self, String>.
2 Likes

Thank you very much for reviewing the code and offering feedback!

  • The tracker actually does sort automatically before starting. The manual command was included because it might be useful in certain situations, and was free to add.
  • I have not come across BTreeMap, I will definitely explore this.
  • Traits are something that have not really "sunk in" yet, so this is good motivation to revisit that topic.
  • This was actually really important for me to hear. I sometimes find myself falling back into old familiar design patterns - it's very helpful to know when this is happening, so I can use the language properly as intended.
  • I had thought putting the terminal code in it's own module (util.rs) was separating it from the logic, but I didn't look at it from a test perspective. I will definitely chew this over some more.

Thank you again, this has been super helpful - much appreciated!

1 Like

One option along these lines would be to think of this as two related but distinct projects:

  1. A reusable library that implements the abstract tracker logic, based in lib.rs, and
  2. A front-end program that presents a terminal interface to the library, based in main.rs.
1 Like

Ah, so completely separate code that handles the terminal interface, and pass data between each?

Would this be a good candidate for a workspace (the tracker is one crate, and the terminal interface is a separate crate)?

A workspace would be a reasonable approach, but is probably overkill at this stage. For now, you can do quite well with the binary and library targets in a single crate— Consider splitting it out into two separate crates if the terminal interface starts getting complicated enough to bring in dependencies that aren't necessary for the library. Or if you just want to play around with workspaces as a learning exercise.

2 Likes

You mean a single package. Every Cargo target is a separate crate always.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.