From discussion on issue on codeberg (last paragraph of the comment ) I understood that code below will no longer work when RLS goes away (code simplified compared to the code in issue) .
Lastly, please note that your code will break when #32009 is implemented, because the initialization of the .rt field will no longer be “visible” before the initialization of the .io field.
To my understanding RLS makes it there is no intermediary copy. Which means the self.* being assigned to and self used within initialization is the same. So in theory this would also currently work thanks to the RLS.
But in the first example, it seems to me it should work even after RLS goes away, because in the .rt initialization I actually modify the original self, and during .io initialization I still use the original self so I do not depend on the original self and the new self being the same.
Am I misunderstanding how it currently works or how it will work after RLS goes away?
EDIT: The first example is maybe not the best one, because self.rt.io() just takes a pointer and so it does not really matter if self.rt is initialized or not. So this modified example can be considered (self.rt now must be initialized otherwise pointer points to wrong location):
Huh, I think I didn’t quite read your code closely enough, I apologise for that. You’re correct that this could would still work with RLS removed due to the explicit in-place initialization of self.rt in the block.
I would, however, say that your code is unnecessarily confusing, and so I’d suggest just doing this elementwise instead, like this:
self.rt = @as(zio.Runtime, undefined); // (this is what we discussed in the issue)
self.rt.?.initStatic(gpa);
self.io = self.rt.io();
Also, if I may make a design suggestion—I wonder whether it’s actually appropriate for this code to own the zio runtime? I’m obviously extrapolating from the code I have seen, so I could be completely off-base—if so, then my apologies, please disregard the rest of this comment. But it kinda looks like you’re writing some code which can support an Io passed by the caller, or it can support a zio.Runtime which it creates itself. But even if zio.Runtime has extra capabilities beyond the Io interface (I’ve not looked into zio before so don’t know), it’d probably be better to still have the caller provide it themselves instead of creating it in your code. For instance, maybe you accept an ?*zio.Runtime and an Io as arguments to this function and let the caller deal with all of the initialization work. The caller probably knows whether or not they want to use zio, so the code on their end is much simpler than what you’re trying to write:
var rt: zio.Runtime = undefined;
try rt.initStatic(gpa);
yourCode(&rt, rt.io());
Just a suggestion, sorry if it doesn’t make sense in context!
Okay, thank you. So I think I am understanding it correctly now.
I dislike this style of initialization because it seems to me like it is easy to introduce bugs like this. If field is added, omitting it with this style of initialization is not a compile error. And it has bitten me few times. So I much prefer the self.* = .{...} with internal in-place initializations because missing field here is a compile error.
Thanks, appreciate it. I think you are right. This structure is an abstraction over logical part of the program where this part is the only thing running on a thread and it in a sense owns the thread and its own runtime. The structure is initialized by many consumers (test programs, tests, actual program), and most of them want the structure to have its own runtime. But rarely I want to run all the program parts single threaded and then it doesn’t own a runtime but takes Io from the main thread runtime. It kinda makes sense to me to have it like this, but it is true I need to improve my design because I am having difficulties with ergonomics of my code. I do need to learn how to separate stuff better.