The Gilded Rose Kata in Rust
Tags: rust, refactoring, kata
Published: 2022-02-06 16:44:58

The Gilded Rose Kata is a refactoring exercise. The full description is available [on GitHub](https://github.com/NotMyself/GildedRose). I became aware of the kata quite late, namely at the SnowCamp conference in 2016. [Johan Martinsson](https://twitter.com/johan_alps) and [Rémi Sanlaville](https://twitter.com/sanlaville) did a live-code refactoring based on the Gilded Rose.

Nowadays, I think that the kata is much more widespread. It's [available](https://github.com/emilybache/GildedRose-Refactoring-Kata) in plenty of languages, even some that are not considered mainstream, _e.g._, <abbr title="eXtensible Stylesheet Language Transformations">XSLT</abbr> or <abbr title="Advanced Business Application Programming">ABAP</abbr>. In this post, I'd like to do it in Rust. My idea is to focus less on general refactoring principles and more on Rust specifics.

## Implementing tests

We need to start the kata by implementing tests to ensure that refactoring doesn't break the existing logic.

There isn't much to say, but still:

* Infinitest:

    IntelliJ IDEA offers the [Infinitest](https://infinitest.github.io/) plugin for JVM languages. You can configure it to run your tests at every code change. As soon as your refactoring breaks the test, the banner turns from green to red. I didn't find any plugin similar for Rust.

* Test location:

    In Java, Maven has popularized the convention over configuration approach, `src/main/java` for application code and `src/test/java` for tests. Usually, the test structure follows the main one. We can write the tests in the same file but in a dedicated module in Rust.

    ```rust
    // Main code
    
    #[cfg(test)]                                                                    // 1
    mod tests {                                                                     // 2
    
        use super::{GildedRose, Item};                                              // 3
    
        #[test]
        pub fn when_updating_regular_item_sell_in_and_quality_should_decrease() {}  // 4
    
        #[test]
        pub fn when_updating_regular_item_quality_should_stop_decreasing_at_0() {}  // 4
    
        // Other tests
    }
    ```

1. Ignored when launched as a regular application
2. Dedicated module
3. Because `tests` is a dedicated module, we need to import `struct` from the parent module
4. Test functions

## Clippy is your friend!

> A collection of lints to catch common mistakes and improve your Rust code.
>
> There are over 500 lints included in this crate!
>
> Lints are divided into categories, each with a default lint level. You can choose how much Clippy is supposed to annoy help you by changing the lint level by category.
>
> -- [GitHub](https://github.com/rust-lang/rust-clippy)

On the command-line, `cargo` integrates Clippy natively. You can use it by running the following command in the project's folder:

```bash
cargo clippy
```

You can display Clippy's warnings inside of IntelliJ. Go to Preferences > Languages & Frameworks > Rust > External Linters. You can then select the tool, _e.g._, Clippy, and whether to run it in the background.


![Intelij IDEA Clippy configuration](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vp1sundslcbs7h8xahwq.png)

IntelliJ warns you that it may be CPU-heavy.

Clippy highlights the following statements:

```rust
item.quality = item.quality + 1;
item.quality = item.quality - 1;
```

As with Java, IntelliJ IDEA is excellent for refactoring. You can use the `Alt+Enter` keys combination, and the IDE will take care of the menial work. The new code is:

```rust
item.quality += 1;
item.quality -= 1;
```

## Functions on implementations

In Java, a large part of the refactoring is dedicated to improve the <abbr title="Object-Oriented">OO</abbr> approach. While Rust is not OO, it offers functions. Functions can be top-level:

```rust
fn increase_quality() {}
```

A function can also be part of an `impl`:

```rust
struct Item {
    pub quality: i32,
}

impl Item {
    fn increase_quality() {}   // 1
}

Item::increase_quality();      // 2
```

1. Define the function
2. Call it

A function defined in an `impl` can get access to its `struct`: its first parameter must be `self` or one of its alternatives - `mut self` and `&mut self`:

```rust
struct Item {
    pub quality: i32,
}

impl Item {
    fn increase_quality(&mut self) {    // 1
        self.quality += 1;              // 2
    }
}

let item = Item { quality: 32 };
item.increase_quality();                // 3
```

1. The first parameter is a mutable reference to the `Item`
2. Update the `quality` property
3. Call the function on the `item` variable

## Matching on strings

The original codebase uses a lot of conditional expressions making string comparisons:

```rust
     if self.name == "Aged Brie" { /* A */}
else if self.name == "Backstage passes to a TAFKAL80ETC concert" { /* B */ }
else if self.name == "Sulfuras, Hand of Ragnaros" { /* C */ }
else { /* D */ }
```

We can take advantage of the `match` keyword. However, Rust distinguishes between the `String` and the `&str` types. For this reason, we have to transform the former to the later:

```rust
match self.name.as_str() {                                       // 1
    "Aged Brie"                                 => { /* A */ }
    "Backstage passes to a TAFKAL80ETC concert" => { /* B */ }
    "Sulfuras, Hand of Ragnaros"                => { /* C */ }
    _                                           => { /* D */ }
}
```

1. Transform `String` to `&str` - required to compile

## Empty match

The quality of the "Sulfuras, Hand of Ragnaros" item is constant over time. Hence, its associated logic is empty. The syntax is `()` to define empty statements.

```rust
match self.name.as_str() {
    "Aged Brie"                                 => { /* A */ }
    "Backstage passes to a TAFKAL80ETC concert" => { /* B */ }
    "Sulfuras, Hand of Ragnaros"                => (),         // 1
    _                                           => { /* D */ }
}
```

1. Do nothing

## Enumerations

Item types are referenced by their name. The refactored code exposes the following lifecycle phases: pre-sell-in, sell-in, and post-sell-in. The code uses the same strings in both the pre-sell-in and post-sell-in phases. It stands to reason to use enumerations to write strings only once.

```rust
enum ItemType {                                        // 1
    AgedBrie,
    HandOfRagnaros,
    BackstagePass,
    Regular
}

impl Item {
  fn get_type(&self) -> ItemType {                     // 2
    match self.name.as_str() {                         // 3
      "Aged Brie"                                 => ItemType::AgedBrie,
      "Sulfuras, Hand of Ragnaros"                => ItemType::HandOfRagnaros,
      "Backstage passes to a TAFKAL80ETC concert" => ItemType::BackstagePass,
      _                                           => ItemType::Regular
    }
  }
}
```

1. Enumeration with all possible item types
2. Function to get the item type out of its name
3. The match on string happens only here. The possibility of typos is in a single location.

At this point, we can use enumerations in match clauses. It requires that the `enum` implements `PartialEq`. With enumerations, we can use a macro.

```rust
#[derive(PartialEq)]
enum ItemType {
    // same as above
}

fn pre_sell_in(&mut self) {
    match self.get_type() {
        ItemType::AgedBrie       => { /* A */ }
        ItemType::BackstagePass  => { /* B */ }
        ItemType::HandOfRagnaros => (),
        ItemType::Regular        => { /* D */ }
    }
}

// Same for post_sell_in
```

## Idiomatic Rust: `From` and `Into`

Because of its strong type system, converting from one type to another is very common in Rust. For this reason, Rust offers two traits in its standard library: `From` and `Into`.

![From and Into traits](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/64crvo7879zvcfxu7fby.png)

> Used to do value-to-value conversions while consuming the input value. It is the reciprocal of `Into`.
>
> One should always prefer implementing `From` over `Into` because implementing `From` automatically provides one with an implementation of `Into` thanks to the blanket implementation in the standard library.
>
> -- [Trait std::convert::From](https://doc.rust-lang.org/std/convert/trait.From.html)

In the section above, we converted a `String` to an `ItemType` using a custom `get_type()` function. To write more idiomatic Rust, we shall replace this function with a `From` implementation:

```rust
impl From<&str> for ItemType {
    fn from(slice: &str) -> Self {
        match slice {
            "Aged Brie"                                 => ItemType::AgedBrie,
            "Sulfuras, Hand of Ragnaros"                => ItemType::HandOfRagnaros,
            "Backstage passes to a TAFKAL80ETC concert" => ItemType::BackstagePass,
            _                                           => ItemType::Regular
        }
    }
}
```

We can now use it:

```rust
fn pre_sell_in(&mut self) {
    match ItemType::from(self.name.as_str())  {       // 1
        ItemType::AgedBrie       => { /* A */ }
        ItemType::BackstagePass  => { /* B */ }
        ItemType::HandOfRagnaros => (),
        ItemType::Regular        => { /* D */ }
    }
}
```

1. Use idiomatic `From` trait

Because implementing `From` provides the symmetric `Into`, we can update the code accordingly:

```rust
fn pre_sell_in(&mut self) {
    match self.name.as_str().into()  {                // 1
        ItemType::AgedBrie       => { /* A */ }
        ItemType::BackstagePass  => { /* B */ }
        ItemType::HandOfRagnaros => (),
        ItemType::Regular        => { /* D */ }
    }
}
```

1. Replace `From` by `into()`

## Conclusion

Whatever the language, refactoring code is a great learning exercise. In this post, I showed several tips to use more idiomatic Rust. As I'm learning the language, feel free to give additional tips to improve the code further.

The complete source code for this post can be found there:

{% github ajavageek/gilded-rose-rust %}

_Originally published at [A Java Geek](https://blog.frankel.ch/start-rust/10/) on February 6<sup>th</sup>, 2021_