Ryan Fleury is a systems programmer who writes about memory management, debuggers, performance, and software economics.
Commenters repeatedly point to his arena allocator article as a great write-up on memory lifetimes and allocation strategies.
I don't think the use of structs to implement vtables in C is abuse, but in this case it's rather pointless. This idiom is used to have dynamic dispatch (e.g. a plugin system), an abstraction (e.g. same interface for file I/O and memory I/O in unit tests), or for ABI-stable vtables in libraries and C++ wrappers.
Instead of `json_parse(&parser_state)` your code calls `parser_state.parse(&parser_state)` in main. Instead of `json_eof(parser_state)` your code calls `parser_state->eof(parser_state)` in the implementation. This is more to type, more irregular (switching from . to ->), and has worse performance. This interface is also not flexible enough to implement a parser for other file formats such as XML. So it doesn't make things easier, and you're not actually making use of dynamic dispatch, so I don't see the point in using this technique.
In general I can not recommend this article for learning about C. I am being lenient about the things the author mentions explicitly, although I find even that mostly hand-waving away too much even for a prototype or short article. For example I have to say it is a bit of a head scratcher for me to hear that JSON is difficult to parse, since the ease of parsing is why JSON won to begin with. The linked article about JSON being a minefield is about underdefined semantics, not parser complexity.
The biggest mistake, and this is sadly hand-waved away as "over-engineering", is the large amount of mallocs and frees, and necessity to track…
> it's quite impressive for a single author to have a functional, fast language with a working garbage collector and arena allocator (with some issues) in only a few years.
As the included code shows, the gc is boehm gc, and checking their repo shows they just include libgc/bdwgc. This is absolutely not a knock against anyone here, it's just about the standard library for this need, and I think it is a far smarter move to use it than for most to attempt to make their own general-purpose gc (though boehm can't catch all leaks).
I feel it would be wrong, however, to characterise this as being a single author having made a language with a gc and arenas, as if those were significant parts of the author's own developments, rather than using a well-picked import and a half-baked implementation of "arenas", which here are really just a global linked list of buffers, freed only at exit, and so everything leaks [0]. They're not really arenas, you can't use them locally in a region of code or as scratchpads, let alone multi-thread it. By their code's own admission, it's just a little pre-allocation to batch mallocs for all the little heap allocations of a GC-assuming codebase, so they're not really arenas like you'd use in C or elsewhere.
Not unimpressive, it's a valid approach for some uses (though not general purpose), it's just different from a language with their own gc and actual arenas. Indeed, just implementing an arena barely even registers in the complexity, I feel, as…
You don't need explicit language support to do this -- it's a fairly common practice in videogame development, but we usually usually call it an arena/scratch buffer. Ryan Fleury has a wonderful article where goes at length about different memory management strategies [1].
It's just a static buffer that you can use for temporary allocations, e.g. to return an array of 50 int32's to a parent stack frame you can just allocate `50*sizeof(int32)` bytes on that buffer and return a pointer to the beginning, instead of a heap-allocated `std::vector<int32>`. Every allocation advances the head of the buffer, so you can have multiple allocations alive at the same time.
Every once in a while you need to free the buffer, which just means resetting its head back to the start - in the case of games the beginning of a new frame is a convenient point to do that.
This doesn't solve dynamic memory in general, as it can't be used for long-lived allocations, it just saves you a bunch of temporary heap allocations. It doesn't solve all problems that RAII does either, as RAII is often used for resources other than memory - files, sockets, locks, ...
[1] https://www.rfleury.com/p/untangling-lifetimes-the-arena-allocator
You can do lot's of the same things in C too, as the author mentions, without too much pain. See for example [1] and [2] on arena allocators (which can be used exactly as the temporary allocator mentioned in the post) and on accepting that the C standard library is fundamentally broken.
From what I can tell, the only significant difference between C and Odin mentioned in the post is that Odin zero-initializes everything whereas C doesn't. This is a fundamental limitation of C but you can alleviate the pain a bit by writing better primitives for yourself. I.e., you write your own allocators and other fundamental APIs and make them zero-initialize everything.
So one of the big issues with C is really just that the standard library is terrible (or, rather, terribly dated) and that there is no drop-in replacement (like in Odin or Rust where the standard library seems well-designed). I think if someone came along and wrote a new C library that incorporates these design trends for low-level languages, a lot of people would be pretty happy.
[1]: https://www.rfleury.com/p/untangling-lifetimes-the-arena-allocator
[2]: https://nullprogram.com/blog/2023/10/08/
> No RAII, no classes, virtual dispatch, smart pointers.
I enjoy programming more when the above concepts are absent. RAII and smart pointers tend toward a fragmented and confused layout of a program's memory—there are much simpler ways!
The arena concept for managing your program's memory is more straightforward. It's easier to think about (not confused) and it becomes natural to have your memory laid out nice and orderly (not fragmented, which can be horrible for performance). See the recent article by Ryan Fleury:
https://www.rfleury.com/p/untangling-lifetimes-the-arena-allocator
I also think life is easier without classes or virtual dispatch. I value a sort of "mathematical elegance" in programming languages, and prefer to create programs from a small set of fundamental language primitives. Classes and virtual dispatch don't earn their keep in that set.
> Though his approach still requires explicit coordination (LaneIdx(), barriers, range distribution) vs BEAM where the scheduler automatically distributes processes across cores.
I don't see how a separate and abstract scheduler handing out tasks is any faster/better.
Being explicit isn't always a bad thing. It lets you better understand and control what is going on, which is very useful. For example: in one of the examples Fleury gave in the aticle, he showed how you could take advantage of this explicitness to avoid having to wait on other threads just to get a new task to work on. (https://www.rfleury.com/i/172146732/dynamically-assigning-many-variable-work-tasks)