Hare by Example: Struct Sub-Typing

Structs can be embedded into other structs which lets you access the sub-structs fields directly when accessing them. This allows you to compose structs based on common fields and allow for code re-use.

use fmt;

type entity_kind = enum {
	PLAYER,
	GOBLIN,
	GREMLIN,
};

// Here we define a group of common properties every entity will have.
type entity = struct {
	kind: entity_kind,
	pos: (int, int),
};

// A player is has all the struct fields an entity has plus a username.
type player = struct {
	entity,
	username: str,
};

// An enemy has all the struct fields an entity has like a player does,
// but instead, it has an hp field.
type enemy = struct {
	entity,
	hp: uint,
};

// This function can make use of the fields common to all entities without
// knowing which specific kind of entity is being used.
fn draw_entity(e: *entity) void = {
	const kind = switch (e.kind) {
	case entity_kind::PLAYER =>
		yield "player";
	case entity_kind::GOBLIN =>
		yield "goblin";
	case entity_kind::GREMLIN =>
		yield "gremlin";
	};
	fmt::printfln("There's a {} at ({}, {})!",
		kind, e.pos.0, e.pos.1)!;
};

export fn main() void = {
	// When initialzing a player we can access the entity fields
	// directly since they have been embedded into the player
	// struct.
	const p1 = player {
		kind = entity_kind::PLAYER,
		pos = (2, 3),
		username = "guts",
	};
	fmt::printf("p1 ({}, {}) {}\n", p1.pos.0, p1.pos.1, p1.username)!;

	// The same goes for the enemy.
	const e1 = enemy {
		kind = entity_kind::GOBLIN,
		pos = (10, 10),
		hp = 1337,
	};
	fmt::printf("e1 ({}, {}) {}\n", e1.pos.0, e1.pos.1, e1.hp)!;

	// Pointers to any subtype of entity can be used as *entity:
	draw_entity(&p1);
	draw_entity(&e1);
};
$ hare run struct-sub-typing.ha
4/4 tasks completed (100%)
p1 (2, 3) guts
e1 (10, 10) 1337
There's a player at (2, 3)!
There's a goblin at (10, 10)!