Optional methods based on state

Hi!
So i tried to think of a way to make some methods depend on the state of an entity. So imagine a goblin in a game for example.

to clarify what i mean, the basic way of doing it:

const Goblin = struct {
  State: enum{ Idle, Figthing} = .Idle,
  //stats and stuff
  fn TalkTo(goblin: *Goblin, frase: []u8) void{
     if(goblin.State == .Idle) 
       //talkstuff
  }

  fn FenceWith(goblin: *Goblin, swordSkill: u8) void {
    if(goblin.State == .Fighting)
      //fightstuff
  }
};

fn InteractWIthGoblin(goblin: *Goblin) void {
  swith(goblin.State) {
    .Idle => goblin.TalkTo("Hello"),
    .Fighting => golbin.FenceWith(mySwordSkill),
  }
}

it works, but i need checks in every method to se if the goblin is in the correct state for that method. Also, it will be a silent bug if i run the method in the wrong state, unless i add more checks.

but i thought of another way with tagged unions:

const Goblin = struct {
  //stats and stuff
  State: union (enum){
    Idle: struct{
      fn TalkTo(goblin: *Goblin, frase: []u8) void{
         //talkstuff
      }
    },
    Fighting: struct{
      fn FenceWith(goblin: *Goblin, swordSkill: u8) void {
        //fightstuff
      }
    },
  },
};

fn InteractWIthGoblin(goblin: *Goblin) void {
  swith(goblin.State) {
    .Idle => |idle| idle.TalkTo(goblin, "Hello"),
    .Fighting => |fighting| fighting.FenceWith(goblin, mySwordSkill),
  }
}

So im pretty happy with that. To be able to call the method, it has to be in the right state to begin with, no need for checks. But, the syntax at the call site is less nice:

goblin.TalkTo("Hello");
//vs
idle.TalkTo(goblin, "Hello");

So i thought i could fix that with @fieldParentPtr :

const Goblin = struct {
  //stats and stuff
  State: union (enum){
    Idle: struct{
      const Self = @This();
      fn TalkTo(idle: *Self, frase: []u8) void{
          const goblin: *Goblin = @fieldParentPtr("State", idle);
         //talkstuff
      }
    },
  },
};

fn InteractWIthGoblin(goblin: *Goblin) void {
  swith(goblin.State) {
    .Idle => |idle| idle.TalkTo("Hello"), // <-- nicer syntax
  }
}

But this gives an error because type of “idle” is not the same as “State”. So is there a way how i can cast, or something, from the unionfield “Idle” to the whole union “State”?

1 Like

In your example Self is double nested into Goblin struct so you need 2 @fieldParentPtr to get goblin.

const print = @import("std").debug.print;

const Goblin = struct {
    name: []const u8,

    State: union(enum) {
        Idle: struct {
            const Self = @This();

            fn TalkTo(idle: *Self, frase: []const u8) void {
                const state: *@FieldType(Goblin, "State") = @fieldParentPtr("Idle", idle);
                const goblin: *Goblin = @alignCast(@fieldParentPtr("State", state));

                print("{s} said {s}\n", .{ goblin.name, frase });
            }
        },
    },
};

pub fn main() !void {
    var gob: Goblin = .{ .State = .Idle, .name = "Andrew" };
    gob.State.Idle.TalkTo("hello");
}
3 Likes

Thanks a lot!