One Crate a Day: has-flag
Tags: rust, javascript, nodemodules, testing
Published: 2023-01-24 18:13:58

---
title: One Crate a Day: has-flag
published: true
description: "One Crate a Day" is a journal through my introductory learnings of the Rust Programming language.
tags: Rust, JavaScript, nodemodules, Testing, rustlang
# Use a ratio of 100:42 for best results.
---

![Pixel art image of a Crab reading a book](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s0mrdc53fblhni1znfyw.png)

Welcome to *"One Crate a Day"*, my daily journal as I dive into the world of Rust programming. As a JavaScript developer with a background in open-source, I've decided to take on the challenge of re-writing popular JavaScript packages for the Rust community. 

Here are a few tl;dr goals I have for this project: 

* 👨‍🎓Learn Rust & Popular JavaScript modules
* 📦Contribute to the Rust community by publishing new Crates
* 👨‍🏫Share my learnings, tips, and best practices with you

*If you've come here to learn how to install, start Rust from scratch, or setup the toolchain for your IDE/environment, see [the Rust Handbook](https://doc.rust-lang.org/stable/book/title-page.html)! Otherwise, lets dive in!*

## Crate #1: [`has-env-flag`](https://crates.io/crates/has-env-flag)
Original Package: [sindresorhus/has-flag](https://github.com/sindresorhus/has-flag)

[Sindre](https://github.com/sindresorhus)'s JavaScript packages exemplify the concepts of single purpose and reusability. Often times only implemented as a single exported function. `has-flag` is no different in this regard. 

### Review

This single function module allows developers to quickly detect the presence of a specific flag in the argv (arguments passed to the script/binary that was run). The code for the module is as follows:

```javascript
import process from 'process'; // eslint-disable-line node/prefer-global/process

export default function hasFlag(flag, argv = process.argv) {
  const prefix = flag.startsWith('-') ? '' : (flag.length === 1 ? '-' : '--');
  const position = argv.indexOf(prefix + flag);
  const terminatorPosition = argv.indexOf('--');
  return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
}

```


With this module, we can easily check for the presence of a specific argument ("flag") based on what was passed into our script. Here's an example from the [README](https://github.com/sindresorhus/has-flag):

```javascript
// foo.js
import hasFlag from 'has-flag';

hasFlag('unicorn');
//=> true

hasFlag('--unicorn');
//=> true

hasFlag('f');
//=> true

hasFlag('-f');
//=> true

hasFlag('foo=bar');
//=> true

hasFlag('foo');
//=> false

hasFlag('rainbow');
//=> false
```

```bash
$ node foo.js -f --unicorn --foo=bar -- --rainbow
```

## Approach
To start my journey, I decided to craft my unit tests first. This way, I could work backwards until I had my solution. 

### Learning #1: Cargo uses Conventions

[Cargo](https://doc.rust-lang.org/cargo/), which is the out-of-the-box tool for running tests, installing packages/dependencies, and more, has **conventions** for building libraries versus binaries. 

By default, Cargo looks for one of two conventions in the workspace:

* Libraries: If you are building a Rust library ("Crate"), Cargo will enforce the presence of `src/lib.rs` in your workspace.

* Binaries: If you are building a Rust binary, Cargo will enforce the presence of `src/main.rs`. 

I love this! Every time I crack open a Rust project, I know _exactly_ where to start/begin reading code. 

### Learning #2: How to write tests

Next, I learned how to write tests in Rust. From all of the packages I've looked at, and according to the [Rust Handbook](https://doc.rust-lang.org/book/ch11-01-writing-tests.html), unit tests are written _in the same file_! Additionally, you'll see the usage of Macros heavily in Rust tests. _Macros are a powerful tool for creating code expansion at compile time, saving you from writing lines of boilerplate code._

Take the following test I wrote in my module as an example:

```rust
// Macro for setting up a test module
#[cfg(test)]
mod tests {
    // Gives access to outer scope (not just the `pub fn`'s)
    // great to test functions used in Dependency Injection
    use super::*;

    // Macro that turns a function into a unit test
    #[test]
    fn args_with_value_not_matching_double_dash() {
        let args = vec!["--foo", "--unicorn=rainbow", "--bar"];
        let expected_value = "unicorn=rainbow";

        assert!(_has_flag(
            args.into_iter().map(ToString::to_string),
            expected_value
        ))
    }
}
```

* [`#[cfg(test)]` macro](https://doc.rust-lang.org/stable/book/ch11-03-test-organization.html?highlight=%23%5Bcfg(test)#the-tests-module-and-cfgtest): This is our macro for setting up our test module. It instructs Rust to compile and run the test code only when you run `cargo test`

* [`#[test]` macro](https://doc.rust-lang.org/stable/book/ch11-01-writing-tests.html): This macro converts the function into a unit test! You will use macros like `assert!()` or `assert_eq!()` to validate the results of the code you want to test.

* `use super::*`: Exposes the outer scope to your [inner test module](https://doc.rust-lang.org/stable/book/ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html). The glob allows _anything_ defined in the outer scope to be used in your test functions (anything not defined with `pub fn`)!

### Learning 3: Working with `std::env`
After implementing the tests I decided to take a crack at writing this module. Much thanks to [Steve Klabnik](https://twitter.com/steveklabnik), who helped me with getting the types for the function and setting up dependency injection, this was the **first** iteration: 

```rust
pub fn has_flag(flag: &str) -> bool {
    _has_flag(std::env::args(), flag)
}

fn _has_flag<I: Iterator<Item = String>>(mut args: I, flag: &str) -> bool {
    let prefix = if flag.starts_with('-') {
        ""
    } else {
        if flag.len() == 1 {
            "-"
        } else {
            "--"
        }
    };

    let position = args.position(|arg| arg == format!("{}{}", prefix, flag));
    let terminator_position = args.position(|arg| arg == "--");
    position.is_some() && (!terminator_position.is_some() || position < terminator_position)
}
```

* [`std::env::args()`](https://doc.rust-lang.org/std/env/fn.args.html): This is how you can access `argv` or arguments that the program was started with. `std` is the [Rust standard library](https://doc.rust-lang.org/std/index.html) and is available to all Rust crates by default.

* [`.position()`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.position): Searches for an element in an iterator, and returns its index. I saw this as   comparable to `.indexOf` in JavaScript. **However I made some mistakes in my code using this and I'll explain why**.  

* [`.is_some()` & `is_none()`](https://doc.rust-lang.org/std/option/enum.Option.html): There is no concept of `null` in Rust. Rather, Option<T> is a type which is meant to handle the no value being returned. In a few parts of our code, we don't _really care what the index is_, rather if the index _exists_. `.is_some()` is the perfect usage for that here. 

### Learning 4: Mutability and Bugs! 
*This code that I initially committed and published actually had a few bugs!* However, my initial tests were passing. I had created a [GitHub Issue](https://github.com/TheLarkInn/has-flag/issues/4) to come back when I had time and implement [all of the tests](https://github.com/sindresorhus/has-flag/blob/0c7d032214c51d14b458364c9f6575ea9afa08b1/test.js#L4-L16) from `has-flag`'s test suite.

My [PR introducing the full tests](https://github.com/TheLarkInn/has-flag/pull/5) was failing so I knew there must be a bug in the code itself. After getting some feedback from the "Beginners" channel on [The Rust Programming Language Discord](https://discord.gg/rust-lang), I started to realize some of the mistakes I had made. Here is the updated code which passed tests:

```rust
pub fn has_flag(flag: &str) -> bool {
    _has_flag(std::env::args(), flag)
}

fn _has_flag<I: Iterator<Item = String>>(args: I, flag: &str) -> bool {
    let prefix = if flag.starts_with('-') {
        ""
    } else if flag.len() == 1 {
        "-"
    } else {
        "--"
    };

    let formatted_flag = format!("{}{}", prefix, flag);
    args.take_while(|arg| arg != "--")
        .any(|arg| arg == formatted_flag)
}
```

* `mut args` => `args`: The first **code smell** I should have noticed when I wrote my first iteration, was that the Rust compiler was forcing me to add `mut` next to my `args` parameter for the function. This didn't make sense to me because `mut` is only needed if I am mutating `args`.

* `.position()` mutates: This led me to noticing that `.position()` was _not_ the function I wanted to use. `.position()` _consumes_ items in the iterator until the predicate is matched. So the code determining `terminator_position` was actually accessing a mutated `args`. All my tests involving the `--` args terminator were failing as a result! 

* [`.take_while()`](https://doc.rust-lang.org/std/iter/struct.TakeWhile.html#method.take_while): Takes an iterator, and runs through it _until_ the predicate is true, and then _returns_ an iterator of those items up until the predicate. **I think of this similar to an iterator _filter_**. This was perfect for us to use here because we don't want to match args passed _after_ the `--` terminator. It also massively simplified our code!

## Ship it🎉
This iteration passed all of our tests and I was able to happily merge and publish a [new release of `has-env-flag`](https://github.com/TheLarkInn/has-flag/releases/tag/0.1.2). As I continue to [work on this journal](https://github.com/users/TheLarkInn/projects/1/views/1?pane=issue&itemId=18591212), I will encounter many new challenges and learnings about Rust, and I can't wait to share them with you. 

## Resources

* [My Rust Crates](https://github.com/stars/TheLarkInn/lists/my-rust-projects) 
* [Rust Journey GitHub Project](https://github.com/users/TheLarkInn/projects/1) 
* [`has-env-flag` project on GitHub](https://github.com/TheLarkInn/has-flag) 
* [`sindresorhus/has-flag` on GitHub](https://github.com/sindresorhus/has-flag)