Enum/struct with hardcoded values

:arrow_down: TL;DR at the end :arrow_down:

Hi there, I'm working on a simple solver for a tile based board game and I'm using an object storing cardinal directions to easily walk through the board. For example, I'm dropping a new tile on the board and I want to check what tile is on the left, conceptually I do new_tile.x + direction.west.x (pseudo-code here).

First test with enum

My first approach on this was to declare an enum to hold all 4 cardinal directions:

enum Direction {
    North,
    East,
    South,
    West,
}

Then what I'd like to do is storing or associate x/y values with enum variants, e.g. North = { x: 0, y: 1 }.
So I added this to the previous code:

impl Direction {
    fn value(&self) -> (i8, i8) {
        match *self {
            Direction::North => (0, 1),
            Direction::East => (1, 0),
            Direction::South => (0, -1),
            Direction::West => (-1, 0),
        }
    }
}

But in the end, I'm never directly calling for an enum variant, I'm always looping through all the directions through an array of values

impl Direction {
    fn values() -> [(i8, i8); 4] {
        [
            Direction::North.value(),
            Direction::East.value(),
            Direction::South.value(),
            Direction::West.value(),
        ]
    }
}

Another approach with struct

Based on my need and what Rust offers, I was thinking about switching to a struct with const fields:

struct Direction {}
impl Direction {
    const NORTH: (i8, i8) = (0, 1);
    const EAST: (i8, i8) = (1, 0);
    const SOUTH: (i8, i8) = (0, -1);
    const WEST: (i8, i8) = (-1, 0);

    fn values() -> [(i8, i8); 4] {
        [
            Direction::NORTH,
            Direction::EAST,
            Direction::SOUTH,
            Direction::WEST,
        ]
    }
}

Because I started to implement functions, it then seemed to me that it was more accurate to use a struct.

What's the best? (TL;DR)

So technically, I can achieve my goal with 2 different writings but conceptually, which object would make more sense to you?

Bonus

While talking about clear way to write things, would it makes sense to convert plain (i8, i8) to struct Vector(i8, i8) or even struct Vector { x: i8, y: i8 }?

Yes, absolutely. Even better, pick a vector math library from one of the many out there (glam is popular). Then instead of new_tile.x + direction.x and new_tile.y + direction.y you write new_tile + direction, which is not just higher-level but also less prone to typos, and encourages avoiding hard-coding axes.

This is an inappropriate use of a struct. The struct isn't used for any purpose but to group the items, so the items should be grouped with a module instead.

use glam::{I8Vec2, i8vec2};

mod direction {
    pub const NORTH: I8Vec2 = i8vec2(0, 1);
    pub const EAST: I8Vec2 = i8vec2(1, 0);
    pub const SOUTH: I8Vec2 = i8vec2(0, -1);
    pub const WEST: I8Vec2 = i8vec2(-1, 0);

    pub fn values() -> [I8Vec2; 4] {
        [
            NORTH,
            EAST,
            SOUTH,
            WEST,
        ]
    }
}

After these changes, you might decide to not use a module either and just have the items. That’s up to you. But don’t create a type that isn’t used as a type.

You have observed that you could create an enum or not. That is basically the most significant part of this design space — you should define the enum if you have a use for it, and not if you don’t.

Personally, writing a lot of grid-based code, I’ve found that eventually, there is a use for the enum, and I have lots of types for different concepts (enum Face which is also a direction; enum Axis { X, Y, Z, }, etc.). But perhaps your solver has narrower needs and won’t ever need the enum.

Don’t worry about it too much — you can always change it later.

7 Likes

I think that one of the thing that was bothering me was the used of not defined type in different places in the code. While my code is small and I know what it refers too, it's more readable to define a simple structure over this.

That was my feeling, thanks for the confirmation :+1:

I didn't though about this option which seems the clearer to me :white_check_mark:

At first, I didn't bother and tried to focus on my code but while I was writing more and more functions using this enum, it started to trigger me and so I decided to changed. Later of the past is now of today :grin: