When I worked in Typescript, I built a lifecycle manager based on a tree of lifecycle components. I found it incredibly useful and it became a pattern I used in every nontrivial application!
Of course, I wanted to migrate this pattern into Zig pretty much right away So I built zig-lifecycle
However, I think I’ve not yet got the hang of writing idiomatic zig code. The code itself is actually really small, it’s only ~230 lines of code including doc strings. If possible, I would really appreciate anyone shining a light on any awkward, verbose, or unusual parts of the code
I would like to get it good enough for people to use reliably, but it likely needs a lot of work, and doesn’t support Windows at the moment. I plan to write what I call “simulation tests“ where the SUT is an entire process, and I inject a random seed into the process-under-test to deterministically inject latency and failures and ensure the process behaves as intended, including failing correctly.
Anyway, I could keep rambling forever let me know if you think you might find it useful
The use of global variables is very discouraged; it makes code less reusable, hides data dependencies and makes future concurrency/async use more difficult.
Also, you’re naming schema is inconsistent and not idiomatic in a couple places.
Package names (in build.zig.zon) shouldn’t contain zig, that is already clear enough by the fact it’s a zig package.
LifecycleNode.zig should be called just Node.zig, your in the lifecycle package repeating the name is just redundant.
Lifecycle.zig should be called Root.zig, and while you’re at it, you could combine it with root.zig (or rename root.zig).
in your example you const Lifecycle = @import("zig-lifecycle"), the capitalisation is incorrect, it is a namespace not an instantiable type.
I genuinely don’t see the point of this, at least in its current state.
It just seems to be sensible normal code structure into an abstraction, why use the abstraction when you can write clearer code manually and in a more flexible manner?
There is no bug in that code; the http server is started after the dB pool, and defer runs in reverse order, so the server is stopped before the DB pool is.
Ah, my example was buggy I edited my previous comment and swapped the order of operations now, but the point is that it’s possible, and someone could accidentally introduce a bug while changing some code
Idiomatic zig is to put the defer as early as possible, doing so prevents your bug without any abstraction needed.
Moving defers together after a bunch of initialisation is somewhat acceptable, but they ofc have to be in the same order as the initialisation.
In small examples its very noticeable when there is a mistake, large examples are themselves an example why you shouldn’t separate the defers from the initialisations.
Shouldn’t and can’t are very different, though. If there’s no tests to catch this particular failure case when it occurs, it can mean a bug gets shipped. I’ve seen plenty of these cases occur in production code, where an engineer makes a change and doesn’t realise the order of some operations had changed inadvertently.
If you make that mistake with the lifecycle manager, the application typically won’t start, and every test case will fail.