fat struct style guide?

Julian Fondren julian.fondren at gmail.com
Tue Feb 24 19:48:07 UTC 2026


On Tuesday, 24 February 2026 at 16:56:57 UTC, monkyyy wrote:
> Its c-like code and enabling "unsafe" void* hacks.
>
> Gist code from last night(I already see one improvement and 
> expect more):
>
> ```d
> #!opend test app
> import std;
> import core.stdc.stdlib;
> import core.sys.posix.dlfcn;
>
> enum animalid{cow=7,chicken,horse};
>
> struct animal{
>     animalid id;
>     string say;
>     int age;
> }
> ...
> ```

An algebraic datatype, a sumtype, a discriminated union, etc. And 
a 'fat pointer' is a just pointer to tagged data with some 
Data-Oriented Design implications about the data. Wikipedia has 
one good line about it:

> A tagged union can be seen as the simplest kind of 
> self-describing data format. The tag of the tagged union can be 
> seen as the simplest kind of metadata.

With std.sumtype a 1:1 version of your code is a real pain, but 
your method would be more painful if the variants weren't so 
similar. std.sumtype's also less painful over time, refusing 
newanimal("idk"), having a compile-time exhaustiveness check. 
There are some rough edges like .each! not working here.

Skipping "do",

```d
import std;
import std.sumtype;

enum animalid {cow = 7, chicken, horse};
struct Cow { string say = "moo"; int age; }
struct Chicken { string say = "cluck"; int age; }
struct Horse { string say = "niah"; int age; }
alias Animal = SumType!(Cow, Chicken, Horse);

animalid id(Animal a) => a.match!(
     (Cow _) => animalid.cow,
     (Chicken _) => animalid.chicken,
     (Horse _) => animalid.horse,
);

int age(Animal a) => a.match!(
     (Cow a) => a.age,
     (Chicken a) => a.age,
     (Horse a) => a.age,
);

void speak(Animal a) => a.match!(
     (Cow a) => writeln("this cow says ", a.say),
     (Chicken a) => writeln("this chicken says ", a.say),
     (Horse a) => writeln("this horse says ", a.say),
);

int[animalid] maxage;

bool isdead(Animal a) => a.age > maxage[a.id];

void passtime(ref Animal a, int years) {
     a.match!(
         (ref Cow a) => a.age += years,
         (ref Chicken a) => a.age += years,
         (ref Horse a) => a.age += years,
     );
}

unittest {
     Animal[] foo;
     foo ~= Cow().Animal;
     foo ~= Chicken().Animal;
     foo ~= Horse().Animal;
     writeln(foo);
     foreach (ref a; foo) a.passtime(10);
     maxage[animalid.chicken] = 5;
     maxage[animalid.cow] = 15;
     maxage[animalid.horse] = 10;
     foo.map!isdead.writeln;
     maxage[animalid.chicken] = 500;
     foo.map!isdead.writeln;
}
```

std.sumtype's more similar to ML or Rust types. Ada and Nim feel 
a lot more like your method:

```nim
import std/[strformat, sequtils]

type
   AnimalKind = enum
     cow, chicken, horse
   Animal = object
     kind: AnimalKind
     say: string
     age: int = 0

# I typo'd "chicken" in this function and got confused when 
nothing died.
func newanimal(s: string): Animal =
   case s
   of "cow": Animal(kind: cow, say: "moo")
   of "chicken": Animal(kind: chicken, say: "cluck")
   else: Animal(kind: horse, say: "niah")

proc speak(a: Animal) = echo &"This {a.kind} says {a.say}"
var maxage: array[AnimalKind, int]
proc old(a: var Animal) = a.age += 10
proc isdead(a: Animal): bool = a.age > maxage[a.kind]

when defined(unittest):
   var foo: seq[Animal]
   foo.add newanimal("chicken")
   foo.add newanimal("cow")
   foo.add newanimal("idk")
   echo foo
   for a in foo: a.speak
   for a in foo.mitems: a.old
   maxage[chicken] = 5
   maxage[cow] = 15
   maxage[horse] = 10
   echo foo.mapIt(it.isdead)
   maxage[chicken] = 500
   echo foo.mapIt(it.isdead)
```


More information about the Digitalmars-d-learn mailing list