30 Days of Rust - Biting off more than I can chew

johnnylarner

johnnylarner

Posted on May 2, 2023

30 Days of Rust - Biting off more than I can chew

Today is the first day of the CLI project outline in my previous article. My optimism was high yesterday, but as I sat down today to write my first lines of code, I found myself struggling to work out what to do. I've taken a step back and want to first try and scope my first features for the tool.

Yesterday we identified two overarching goals or epics for the project:

  1. CLI installation and arg parsing;
  2. Process management and meta data collection.

I'm now going to drilldown on objective 2 to try and find a bit sized task to tackle today.

Where do I begin??

Our CLI tool is going to need a name. Now I already found that my chosen project name had already been taken on crates (😡) so I opted for time-rs.

Now that we have a name we can take a look for some templates. I found a solid project template on Github. My only gripe so far is that it doesn't include any pre-commit hooks of the box.

I've selected my licence and deleted a bunch of superfluous contribution guideline stuff. With the easy bits done, now let's start thinking about code.

What do we need from our processes and why?

Compatibility

The aim of this CLI tool is to be able to time the duration of an arbitrary command run on an operating system. This leads to some pretty obvious characteristics of the tool:

  1. To run an arbitrary command, it must function platform-agnostically
  2. Whatever structured response it yields, it must be the same across all platforms

To make ground on these issues, we'll need to ensure that we can run our unit tests in different environments. Fortunately, the template I used includes this in the Github Action out of the box. That said, until we have a proper CLI package, we'll need to test our code using some kind of OS based parameterisation.

Consistent response data

Providing a useful and consistent response will be key to making the tool easy to use for developers:

  1. Ideally, the time measurement should be as accurate as possible. This may entail subtracting the difference from opening and closing a process. Perhaps this also indicates that any timer should be located close in the call stack to where the process is opened.
  2. Most commands will also leverage stdout and stderr (let's not worry about stdin right now). This will probably be of use for the user. But it may make presenting our timed output more challenging. Perhaps we need to differentiate somehow.
  3. Do we care about making our response machine parsable? This would allow our users to make automated decisions based on the results.

Many of these attributes are too big to tackle in one go, but I think we're making some progress filtering down to a task level.

So many features, such little time

Before we even can ask the question of how accurate time-rs is, we need to be able to open processes in Rust. It also needs to be able to retrieve the stdout and stderr of a process. We may not always want to feed it up to our user, but having it available to us will be useful.

Formulated as concrete tasks:

  • [ ] time-rs has a function that can launch a process with any command
  • [ ] We can read and test the stdout and stderr of a command

std::process or extern crate subprocess?

Now we have two clear tasks, we have to decide what tools to use to solve the problem. One of my goals with this tool was to learn the standard library better. So let's take a look at std::process.

The module coalesces around the Command struct. In chaining method calls, we can easily setup a child process and even handle errors inline:

use std::process::Command;

let output = Command::new("echo")
                     .arg("Hello world")
                     .output()
                     .expect("Failed to execute command");

assert_eq!(b"Hello world\n", output.stdout.as_slice());
Enter fullscreen mode Exit fullscreen mode

One issue I'm seeing with this API is the .arg method. How can I dynamically generate method calls at runtime? Is the process only run when I call the .output or .spawn method? Also, in order to chain calls, I'll need to be able to determine what arguments and flags have been specified by the user.

This could be one advantage of using the subprocess third-party library. It contains the Exec::shell which allows you to pass &str commands which are then used to build a Popen instance. Interestingly, the authors of the library base the library's design on Python's standard library subprocess module.

Though this might be a convenient solution, a quick google revealed to me the clap CLI project (mentioned actually in the official docs). I think this will provide the functionality neededto derive arguments from our users' commands such that we won't need to rely on subprocess. Also there has been little activity on the subprocess github repo, which makes me feel uncertain about selecting it as a tool.

So with those questions cleared up, I'll start writing code tomorrow 💪

💖 💪 🙅 🚩
johnnylarner
johnnylarner

Posted on May 2, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related