digitalmars.D.learn - fat struct style guide?
- monkyyy (3/3) Feb 23 Im going to try doing something with fat structs, what sort of
- Kapendev (6/9) Feb 24 First of all, it's a plus-size struct.
- monkyyy (4/6) Feb 24 Magic data structure to base games around; looking for
- H. S. Teoh (5/7) Feb 24 What's a "fat struct"?
- matheus (62/69) Feb 24 In programming, the term "fat struct" (or "fatty struct") refers
- monkyyy (5/11) Feb 24 Fundmentally your still having massive ambiguities about types
- monkyyy (8/15) Feb 24 Something like a "entity" that has a first member is a id
- H. S. Teoh (8/19) Feb 24 [...]
- monkyyy (6/24) Feb 24 Related ideas, but ecs will definitely have components which is a
- H. S. Teoh (16/34) Feb 24 [...]
- monkyyy (92/127) Feb 24 Your thinking with oo and not type theory and anti-safetyism; it
- user1234 (8/22) Feb 24 The tagged union approach is actually faster when you deal with a
- monkyyy (6/15) Feb 24 Consider the `old` function, a tagged union would add a layer of
- Julian Fondren (14/16) Feb 24 ```d
- Kapendev (9/23) Feb 24 You get the same type of indirection with a "mega struct" if we
- Kapendev (4/12) Feb 24 You can optimize both for size, but anyway. In D, it is so much
- monkyyy (8/32) Feb 24 only with a pretty void* cast is accessing a tagged union field
- Kapendev (25/37) Feb 24 ```d
- monkyyy (3/29) Feb 24 Thats a compromise between fat structs and components, everywhere
- Kapendev (13/46) Feb 24 The point is that it's the same stuff, but with less fat.
- Julian Fondren (8/12) Feb 24 Some other options: using a common struct member for the common
- monkyyy (5/17) Feb 24 I dont wanta do soa without compiler help, it hard, Ive tried;
- Julian Fondren (95/114) Feb 24 An algebraic datatype, a sumtype, a discriminated union, etc. And
- monkyyy (32/75) Feb 24 Its an *intrusive* sumtype, fatpointers have different tradeoffs.
Im going to try doing something with fat structs, what sort of syntax and api niceitys would big fat-struct advocates say are nessery?
Feb 23
On Tuesday, 24 February 2026 at 03:48:46 UTC, monkyyy wrote:Im going to try doing something with fat structs, what sort of syntax and api niceitys would big fat-struct advocates say are nessery?First of all, it's a plus-size struct. But no idea really. What is your use case? The common think I see when poeple talk about them for games is bit flags. Maybe a way to avoid manually dealing with them would be nice.
Feb 24
On Tuesday, 24 February 2026 at 15:25:14 UTC, Kapendev wrote:But no idea really. What is your use case?Magic data structure to base games around; looking for hotreloading, proper usage of the term fail-safe and intrusive sumtypes
Feb 24
On Tue, Feb 24, 2026 at 03:48:46AM +0000, monkyyy via Digitalmars-d-learn wrote:Im going to try doing something with fat structs, what sort of syntax and api niceitys would big fat-struct advocates say are nessery?What's a "fat struct"? T -- Those who don't understand Unix are condemned to reinvent it, poorly.
Feb 24
On Tuesday, 24 February 2026 at 15:30:39 UTC, H. S. Teoh wrote:On Tue, Feb 24, 2026 at 03:48:46AM +0000, monkyyy via Digitalmars-d-learn wrote:In programming, the term "fat struct" (or "fatty struct") refers to a data structure design philosophy, notably advocated by programmer Casey Muratori, that prioritizes data locality, performance, and predictability over object-oriented design principles like deep inheritance hierarchies. [1, 2] Core Principles The fat struct approach represents a fundamental difference in how software development priorities are ordered, focusing on efficiency over abstract ideals. • Packs Related Data: It involves grouping all related data into a single, contiguous block of memory within one structure, rather than spreading it across multiple objects that require pointers and indirection to access. • Prioritizes Cache Efficiency: By keeping frequently accessed data physically close in memory, this design makes better use of modern CPU caches, leading to significant performance improvements. • Avoids Unnecessary Abstraction: It eschews complex object-oriented programming (OOP) patterns, preferring simple control flow and a design that more directly reflects what the computer hardware is actually doing. • Uses Unions Judiciously: While the primary goal is a single structure, unions might be used to save space if some fields are mutually exclusive, though this can make debugging more complex. [2, 3] Comparison to OOP The fat struct approach is often contrasted with the traditional OOP paradigm where specialized classes inherit properties and methods from a base class. • Inheritance vs. Type Property: Instead of creating multiple specialized classes (e.g., and inheriting from ), a single "fat" struct would contain all possible properties (like and ) and use an explicit field to determine which behaviors and data are relevant. • Performance Trade-off: The OOP approach is often seen as more maintainable and easier to extend, while the fat struct approach trades some of that perceived maintainability for raw performance. • Data-Oriented Design: The philosophy is a key component of data-oriented design (DOD), where the focus is on how data is laid out in memory and accessed, rather than modeling real-world entities with class hierarchies. [1, 2, 4, 5] Practical Implementation Tips If you are interested in applying these principles, advocates suggest a pragmatic approach: 1. Start with Data Access Patterns: Design your structures around how your code actually uses the data, not in an abstract ideal. 2. Measure Performance: The benefits of this approach should be measurable; benchmark before and after implementation to ensure the trade-off is worthwhile. 3. Document Layout Decisions: Clearly document the rationale behind your memory layouts for future maintainers. [6, 7] [1] https://orangegnome.com/posts/3633/team-fat-posts [2] https://levelup.gitconnected.com/fatty-structs-casey-muratoris-approach-to-data-structure-design-ad4753caaf6b [3] https://www.computerenhance.com/p/q-and-a-80-2025-10-31 [4] https://www.yegor256.com/2020/02/19/fat-skinny-design.html [5] https://www.reddit.com/r/roguelikedev/comments/1bv4k7p/any_tutorial_about_dataoriented_programming_for/ [6] https://levelup.gitconnected.com/fatty-structs-casey-muratoris-approach-to-data-structure-design-ad4753caaf6b [7] https://levelup.gitconnected.com/fatty-structs-casey-muratoris-approach-to-data-structure-design-ad4753caaf6b All the content above was generated by AI, I'm just annexing to the post to future readers. Matheus.Im going to try doing something with fat structs, what sort of syntax and api niceitys would big fat-struct advocates say are nessery?What's a "fat struct"? T
Feb 24
On Tuesday, 24 February 2026 at 15:49:50 UTC, matheus wrote:Comparison to OOP The fat struct approach is often contrasted with the traditional OOP paradigm where specialized classes inherit properties and methods from a base class. All the content above was generated by AI, I'm just annexing to the post to future readers.Fundmentally your still having massive ambiguities about types and call site rituals, it may not be the classical problems with oo, but your not getting the terseness of functional programming. I think this is a bad take.
Feb 24
On Tuesday, 24 February 2026 at 15:30:39 UTC, H. S. Teoh wrote:On Tue, Feb 24, 2026 at 03:48:46AM +0000, monkyyy via Digitalmars-d-learn wrote:Something like a "entity" that has a first member is a id allowing for intrusive sumtypes from void* pointers or a giant union and just allot of data. https://youtu.be/ShSGHb65f3M?si=sEbxLfwvMMSXQFcG&t=425 The german c-extremist guy I think is the source of the term. (not casey or redshirt); im a bit fuzzy on it, it seems like a compromise with oo to me.Im going to try doing something with fat structs, what sort of syntax and api niceitys would big fat-struct advocates say are nessery?What's a "fat struct"? T
Feb 24
On Tue, Feb 24, 2026 at 03:56:20PM +0000, monkyyy via Digitalmars-d-learn wrote:On Tuesday, 24 February 2026 at 15:30:39 UTC, H. S. Teoh wrote:[...]On Tue, Feb 24, 2026 at 03:48:46AM +0000, monkyyy via Digitalmars-d-learn wrote:Im going to try doing something with fat structs, what sort of syntax and api niceitys would big fat-struct advocates say are nessery?What's a "fat struct"?Something like a "entity" that has a first member is a id allowing for intrusive sumtypes from void* pointers or a giant union and just allot of data.[...] So is that like ECS or something along those lines? *Entity-Component-System. T -- Artificial intelligence will never be a match for natural stupidity.
Feb 24
On Tuesday, 24 February 2026 at 16:19:05 UTC, H. S. Teoh wrote:On Tue, Feb 24, 2026 at 03:56:20PM +0000, monkyyy via Digitalmars-d-learn wrote:Related ideas, but ecs will definitely have components which is a whole thing of complexity and makes for scheduling and you start talking about custom compilers. All good ecs will probably be fat structs, but components are not necessarily members of good entities.On Tuesday, 24 February 2026 at 15:30:39 UTC, H. S. Teoh wrote:[...]On Tue, Feb 24, 2026 at 03:48:46AM +0000, monkyyy via Digitalmars-d-learn wrote:Im going to try doing something with fat structs, what sort of syntax and api niceitys would big fat-struct advocates say are nessery?What's a "fat struct"?Something like a "entity" that has a first member is a id allowing for intrusive sumtypes from void* pointers or a giant union and just allot of data.[...] So is that like ECS or something along those lines? *Entity-Component-System. T
Feb 24
On Tue, Feb 24, 2026 at 04:21:01PM +0000, monkyyy via Digitalmars-d-learn wrote:On Tuesday, 24 February 2026 at 16:19:05 UTC, H. S. Teoh wrote:[...]On Tue, Feb 24, 2026 at 03:56:20PM +0000, monkyyy via Digitalmars-d-learn wrote:On Tuesday, 24 February 2026 at 15:30:39 UTC, H. S. Teoh wrote:[...][...]What's a "fat struct"?Something like a "entity" that has a first member is a id allowing for intrusive sumtypes from void* pointers or a giant union and just allot of data.[...] So is that like ECS or something along those lines?Related ideas, but ecs will definitely have components which is a whole thing of complexity and makes for scheduling and you start talking about custom compilers. All good ecs will probably be fat structs, but components are not necessarily members of good entities.Hmm. So basically the idea is to stuff everything into a single struct so that you keep everything in cache instead of having to dereference pointers? Sounds like something you could use compile-time introspection for. Say you have some kind of compile-time spec of what sub-structs go into some entity, then have compile-time code auto-generate the fat struct that contains everything in these sub-structs. Could do it in a way that makes it easy to change at the code level without editing tons of struct definitions (by using CTFE to generate said definitions and then mix them in). T -- "If this is the solution, I want my precipitate back."
Feb 24
On Tuesday, 24 February 2026 at 16:45:30 UTC, H. S. Teoh wrote:On Tue, Feb 24, 2026 at 04:21:01PM +0000, monkyyy via Digitalmars-d-learn wrote:Your thinking with oo and not type theory and anti-safetyism; it would be a style guide and acceptable callsite usage patterns; not a meta-program. C-sytle code needs a compiler that handles offsets of structs and void* an oo-compiler needs an enforced type theory and indirection about every turn. When you say "substructs" your imagining enforced type theory, when you say "autogenerate the fat struct" your ideas would then nesserisate type-indirection at the function level that would be very very complex to metaprogram or some new compiler feature like ecs. Its c-like code and enabling "unsafe" void* hacks. Gist code from last night(I already see one improvement and expect more): ```d 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; } void apply(string s,T...)(ref animal a,T args){ static void afunction(ref animal,T); alias F=typeof(afunction); Switch: switch(a.id){ static foreach(C;EnumMembers!animalid){ case C: (cast(F*)dlsym(null,s~C.stringof))(a,args); break Switch; } default: break; } } animal newanimal(string s){ if(s=="cow"){ return animal(animalid.cow,"moo"); } if(s=="chicken"){ return animal(animalid.chicken,"cluck"); } return animal(animalid.horse,"niah"); } void speak(animal a){ writeln("this ",a.id," says ",a.say); } extern(C) void docow(ref animal a){ "eat grass".writeln; a.age++; } extern(C) void dochicken(ref animal a){ "attack zelda".writeln; a.age++; } extern(C) void dohorse(ref animal a){ "run".writeln; a.age++; } void makechicken(ref animal a){ a.id=animalid.chicken; } void old(ref animal a){ a.age+=10; } int[animalid] maxage; bool isdead(animal a){ return a.age>maxage[a.id]; } unittest{ animal[] foo; foo~=newanimal("cow"); foo~=newanimal("chicken"); foo~=newanimal("idk"); foo.writeln; foo.each!speak; foo.each!(apply!"do"); //--- foo.each!old; maxage[animalid.chicken]=5; maxage[animalid.cow]=15; maxage[animalid.horse]=10; foo.map!isdead.writeln; maxage[animalid.chicken]=500; foo.map!isdead.writeln; //--- foo.each!makechicken; foo.each!speak; } ```On Tuesday, 24 February 2026 at 16:19:05 UTC, H. S. Teoh wrote:[...]On Tue, Feb 24, 2026 at 03:56:20PM +0000, monkyyy via Digitalmars-d-learn wrote:On Tuesday, 24 February 2026 at 15:30:39 UTC, H. S. Teoh wrote:[...][...]What's a "fat struct"?Something like a "entity" that has a first member is a id allowing for intrusive sumtypes from void* pointers or a giant union and just allot of data.[...] So is that like ECS or something along those lines?Related ideas, but ecs will definitely have components which is a whole thing of complexity and makes for scheduling and you start talking about custom compilers. All good ecs will probably be fat structs, but components are not necessarily members of good entities.Hmm. So basically the idea is to stuff everything into a single struct so that you keep everything in cache instead of having to dereference pointers? Sounds like something you could use compile-time introspection for. Say you have some kind of compile-time spec of what sub-structs go into some entity, then have compile-time code auto-generate the fat struct that contains everything in these sub-structs. Could do it in a way that makes it easy to change at the code level without editing tons of struct definitions (by using CTFE to generate said definitions and then mix them in). T
Feb 24
On Tuesday, 24 February 2026 at 16:56:57 UTC, monkyyy wrote:Your thinking with oo and not type theory and anti-safetyism; it would be a style guide and acceptable callsite usage patterns; not a meta-program. C-sytle code needs a compiler that handles offsets of structs and void* an oo-compiler needs an enforced type theory and indirection about every turn. When you say "substructs" your imagining enforced type theory, when you say "autogenerate the fat struct" your ideas would then nesserisate type-indirection at the function level that would be very very complex to metaprogram or some new compiler feature like ecs. Its c-like code and enabling "unsafe" void* hacks. Gist code from last night(I already see one improvement and expect more):The tagged union approach is actually faster when you deal with a situation such as - you only care about final types - your final types have several bases - you would cast a lot - you don't care about nicely looking declarations (e.g SDL event, XEvent, etc 🤢)
Feb 24
On Tuesday, 24 February 2026 at 18:49:58 UTC, user1234 wrote:On Tuesday, 24 February 2026 at 16:56:57 UTC, monkyyy wrote:Consider the `old` function, a tagged union would add a layer of indirection that does nothing. This wont be faster, it may only tie if the compiler untangles the mess that was made. A 3d engine will have lots of things, but then have a draw loop that like "I dont care whats your model id and position"[...]The tagged union approach is actually faster when you deal with a situation such as - you only care about final types - your final types have several bases - you would cast a lot - you don't care about nicely looking declarations (e.g SDL event, XEvent, etc 🤢)
Feb 24
On Tuesday, 24 February 2026 at 19:01:35 UTC, monkyyy wrote:Consider the `old` function, a tagged union would add a layer of indirection that does nothing.```d enum animalid{cow=7,chicken,horse}; struct animal{ animalid id; string say; int age; } ``` This is a tagged union. Both the D and the Nim that I posted have a check against the tag that's technically unnecessary, but what you're doing here is like saying that you don't want to use arrays "because array indexing comes with an array bounds check and that slows down access".
Feb 24
On Tuesday, 24 February 2026 at 19:01:35 UTC, monkyyy wrote:On Tuesday, 24 February 2026 at 18:49:58 UTC, user1234 wrote:You get the same type of indirection with a "mega struct" if we are talking about type checking. And you will be checking types often in a game. There is literally no difference in how a tagged union and a mega struct works if you create a union with members that all share the same first field. The only difference will be the size, with tagged unions being smaller and faster to iterate over.On Tuesday, 24 February 2026 at 16:56:57 UTC, monkyyy wrote:Consider the `old` function, a tagged union would add a layer of indirection that does nothing.[...]The tagged union approach is actually faster when you deal with a situation such as - you only care about final types - your final types have several bases - you would cast a lot - you don't care about nicely looking declarations (e.g SDL event, XEvent, etc 🤢)
Feb 24
On Tuesday, 24 February 2026 at 21:37:44 UTC, Kapendev wrote:You get the same type of indirection with a "mega struct" if we are talking about type checking. And you will be checking types often in a game. There is literally no difference in how a tagged union and a mega struct works if you create a union with members that all share the same first field. The only difference will be the size, with tagged unions being smaller and faster to iterate over.You can optimize both for size, but anyway. In D, it is so much easier to use a tagged union compared to something like C, so why not use it?
Feb 24
On Tuesday, 24 February 2026 at 21:37:44 UTC, Kapendev wrote:On Tuesday, 24 February 2026 at 19:01:35 UTC, monkyyy wrote:only with a pretty void* cast is accessing a tagged union field that happens to line up 0 indirection, everyone here is going to tell me to do the safe then and write an abstracted getter that gets the .offsetof `age`. If Im dropping the concept of different layouts, its safe and doesnt need the abstraction and not repeating myself defining the fields.On Tuesday, 24 February 2026 at 18:49:58 UTC, user1234 wrote:You get the same type of indirection with a "mega struct" if we are talking about type checking. And you will be checking types often in a game. There is literally no difference in how a tagged union and a mega struct works if you create a union with members that all share the same first field. The only difference will be the size, with tagged unions being smaller and faster to iterate over.On Tuesday, 24 February 2026 at 16:56:57 UTC, monkyyy wrote:Consider the `old` function, a tagged union would add a layer of indirection that does nothing.[...]The tagged union approach is actually faster when you deal with a situation such as - you only care about final types - your final types have several bases - you would cast a lot - you don't care about nicely looking declarations (e.g SDL event, XEvent, etc 🤢)
Feb 24
On Tuesday, 24 February 2026 at 21:56:47 UTC, monkyyy wrote:```d // Tagged union. struct Base { int x, y, w, h; ubyte type; } struct Foo { Base base; int hp; } struct Goo { Base base; string name; } union Entity1 { Base base; Foo foo; Goo goo; } // MEGA struct. struct Entity2 { int x, y, w, h; int hp; string name; ubyte type; } void main() { auto e1 = Entity1(); e1.base.x += 1; // Can just do that without checking or void magic. auto e2 = Entity2(); e2.x += 2; // It's the same thing. import std.stdio; writeln("Entity1 size: ", e1.sizeof); writeln("Entity2 size: ", e2.sizeof); } ```You get the same type of indirection with a "mega struct" if we are talking about type checking. And you will be checking types often in a game. There is literally no difference in how a tagged union and a mega struct works if you create a union with members that all share the same first field. The only difference will be the size, with tagged unions being smaller and faster to iterate over.only with a pretty void* cast is accessing a tagged union field that happens to line up 0 indirection, everyone here is going to tell me to do the safe then and write an abstracted getter that gets the .offsetof `age`.
Feb 24
On Tuesday, 24 February 2026 at 22:10:08 UTC, Kapendev wrote:On Tuesday, 24 February 2026 at 21:56:47 UTC, monkyyy wrote:Thats a compromise between fat structs and components, everywhere you have base it acts fat[...]```d // Tagged union. struct Base { int x, y, w, h; ubyte type; } struct Foo { Base base; int hp; } struct Goo { Base base; string name; } union Entity1 { Base base; Foo foo; Goo goo; } // MEGA struct. struct Entity2 { int x, y, w, h; int hp; string name; ubyte type; } void main() { auto e1 = Entity1(); e1.base.x += 1; // Can just do that without checking or void magic. auto e2 = Entity2(); e2.x += 2; // It's the same thing. import std.stdio; writeln("Entity1 size: ", e1.sizeof); writeln("Entity2 size: ", e2.sizeof); } ```
Feb 24
On Tuesday, 24 February 2026 at 22:27:32 UTC, monkyyy wrote:On Tuesday, 24 February 2026 at 22:10:08 UTC, Kapendev wrote:The point is that it's the same stuff, but with less fat. There is only one thing mega sturcts do better and that is you not needing functions like: ```d // Ensure at compile time that all types in the union have the same base. static assert(Entity.isBaseAliasingSafe); // Helpers for converting normal types to entity types. static foreach (T; Entity.Types) { Entity xx(T value) => Entity(value); } ```On Tuesday, 24 February 2026 at 21:56:47 UTC, monkyyy wrote:Thats a compromise between fat structs and components, everywhere you have base it acts fat[...]```d // Tagged union. struct Base { int x, y, w, h; ubyte type; } struct Foo { Base base; int hp; } struct Goo { Base base; string name; } union Entity1 { Base base; Foo foo; Goo goo; } // MEGA struct. struct Entity2 { int x, y, w, h; int hp; string name; ubyte type; } void main() { auto e1 = Entity1(); e1.base.x += 1; // Can just do that without checking or void magic. auto e2 = Entity2(); e2.x += 2; // It's the same thing. import std.stdio; writeln("Entity1 size: ", e1.sizeof); writeln("Entity2 size: ", e2.sizeof); } ```
Feb 24
On Tuesday, 24 February 2026 at 21:56:47 UTC, monkyyy wrote:only with a pretty void* cast is accessing a tagged union field that happens to line up 0 indirection, everyone here is going to tell me to do the safe then and write an abstracted getter that gets the .offsetof `age`.Some other options: using a common struct member for the common cases, and rotating the struct so that you have a struct-of-arrays (SoA) instead of an array of structs. Whether these are wins depends on what you're doing exactly. If the variants have very different fields, SoA stops making sense fast. But to add 10 all the ages you would have a function that modifies an int[]
Feb 24
On Tuesday, 24 February 2026 at 22:11:36 UTC, Julian Fondren wrote:On Tuesday, 24 February 2026 at 21:56:47 UTC, monkyyy wrote:I dont wanta do soa without compiler help, it hard, Ive tried; its in theory 8x maybe 16x when the standard code is 10000x slower then what I write. 10x complexity doesnt seem worth it.only with a pretty void* cast is accessing a tagged union field that happens to line up 0 indirection, everyone here is going to tell me to do the safe then and write an abstracted getter that gets the .offsetof `age`.Some other options: using a common struct member for the common cases, and rotating the struct so that you have a struct-of-arrays (SoA) instead of an array of structs. Whether these are wins depends on what you're doing exactly. If the variants have very different fields, SoA stops making sense fast. But to add 10 all the ages you would have a function that modifies an int[]
Feb 24
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 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 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) ```
Feb 24
On Tuesday, 24 February 2026 at 19:48:07 UTC, Julian Fondren wrote:On Tuesday, 24 February 2026 at 16:56:57 UTC, monkyyy wrote:Its an *intrusive* sumtype, fatpointers have different tradeoffs. Also see me asking for help with intrusive lists.Its c-like code and enabling "unsafe" void* hacks. Gist code from last night(I already see one improvement and expect more): ```d 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:They are fat structs because you could have like 100 members. There are ~3 details that I think can matter for overloading, layout, different behavior, size. Fat structs are making tradeoffs for layout similarity and I expect dividends on the subject.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.Compile time exhaustiveness is a major tradeoff, even for correctness focused code. ```d struct foo{ string me; bar* left; bar* right; } alias bar=SumType!(null,int,float,bool,foo); ``` The recursion is hard to resolve; not impossible and I could suggest 3 ideas, but still, this doesn't work with the 3 sumtypes phoboes shipped.(1 of them was unjustifiably bad at this)Skipping "do",`Do` is the main point here, if you call `dlsym` your half way to hotreloading. That sort of demo doesnt work well with a gist tho.int age(Animal a) => a.match!( (Cow a) => a.age, (Chicken a) => a.age, (Horse a) => a.age, );This is painful and would disqualify it if it was required, while snar didnt get an `apply!(a=>a.age)` into phoboes it should be entirely possible, I may have it somewhere, theres probably a supported 2 line solution as well. My sumtype gists would be able to implement it trivially.unittest { } ```You also skipped "makechicken" and the effect that had on "say" Its fun is a chicken says moo because it was raised a cow; videogame logic, why not.
Feb 24









monkyyy <crazymonkyyy gmail.com> 