How to implement a "next/previous" function for enums?

While implementing a function to model traversing a maze, I tried creating an enum to represent headings:

const Heading = enum {
    north,
    east,
    south,
    west,

    pub fn turn_left(self: Heading) Heading {
        const int_val = @intFromEnum(self);
        return @typeInfo(Heading).@"enum".fields[int_val + 3 % 4];
    }

    pub fn turn_right(self: Heading) Heading {
        const int_val = @intFromEnum(self);
        return @typeInfo(Heading).@"enum".fields[int_val + 1 % 4];
    }
};

However, this gave an error (pointing to the contents of the square-brackets): values of type '[]const builtin.Type.EnumField' must be comptime-known, but index value is runtime-known

I think this means that I’m not allowed to index into @"enum".fields with a value that could vary at runtime, but only values that could be computed at compilation time. Is there a way, then, to (at runtime) find “the enum entry which is next(/previous) in the list from the current one”? This suggests that there used to be a built-in named @intToEnum, but that doesn’t appear to exist anymore.


I suppose I could do this like so:

const Heading = enum {
    north,
    east,
    south,
    west,
    pub fn turn_right(self: Heading) {
        return switch(self) {
            Heading.north => Heading.east,
            Heading.east => Heading.south,
            ...
        }
    }
    pub fn turn_left(self: Heading) {
        return switch(self) {
        ....
        }
    }
}

but that feels repetitive of concepts that are already encoded in the enum itself.

Ah - it became @enumFromInt.

Using enumFromInt with a increment/decrement carries the implicit assumption that the values of the enum are monotonic, which happens to be the case in this example, but enum doesn’t guarantee that (easy to assign different numbers to the enum values), so this solution isn’t general.

1 Like

you can also be completely overkill and do it like so :slight_smile:

const std = @import("std");

pub const Heading = enum {
    north,
    east,
    south,
    west,

    const forward: std.EnumMap(Heading, Heading) = blk: {
        const transtion_forwards = std.EnumMap(Heading, Heading).init(.{
            .north = .east,
            .east = .south,
            .south = .west,
            .west = .north,
        });

        break :blk transtion_forwards;
    };

    const backward: std.EnumMap(Heading, Heading) = blk: {
        const transtion_forwards = std.EnumMap(Heading, Heading).init(.{
            .north = .west,
            .west = .south,
            .south = .east,
            .east = .north,
        });

        break :blk transtion_forwards;
    };

    pub fn next(self: Heading) Heading {
        return forward.get(self) orelse unreachable;
    }

    pub fn prev(self: Heading) Heading {
        return backward.get(self) orelse unreachable;
    }
};

pub fn main() !void {
    var heading_forward: Heading = .north;
    var heading_backward: Heading = .north;

    for (0..10) |_| {
        std.debug.print("{s} : ", .{@tagName(heading_forward.next())});
        heading_forward = heading_forward.next();

        std.debug.print("{s}\n", .{@tagName(heading_backward.prev())});
        heading_backward = heading_backward.prev();
    }
}
2 Likes