Does std.process.Init.environ_map allocate memory or not?

The doc comment for it says “Environment variables, initialized with gpa" so when I used it first time I did the following

const environ_map = init.environ_map;
defer environ_map.deinit();

But it failed …

thread 16198 panic: integer overflow
Unwind error at address `./development:0x2dc2f6` (unwind info unavailable), remaining frames may be incorrect
???:?:?: 0x3e12ff in multi_array_list.MultiArrayList(array_hash_map.Custom([ ]const u8,[ ]const u8,process.Environ.Map.EnvNameHashContext,false).Data).slice (./development)
???:?:?: 0x3e1ff0 in multi_array_list.MultiArrayList(array_hash_map.Custom([ ]const u8,[ ]const u8,process.Environ.Map.EnvNameHashContext,false).Data).items__anon_24407 (./development)
???:?:?: 0x3e1f28 in array_hash_map.Custom([ ]const u8,[ ]const u8,process.Environ.Map.EnvNameHashContext,false).keys (./development)
???:?:?: 0x3e1df6 in process.Environ.Map.keys (./development)
???:?:?: 0x44761e in process.Environ.Map.deinit (./development)
???:?:?: 0x442e83 in start.posixCallMainAndExit (./development)
???:?:?: 0x441bf1 in start.\_start (./development)
zsh: abort      ./development

I removed the deinit part and it was fine(I also tried making environ_map mutable).
Another thing that added to the confusion is …

const environ = init.minimal.environ;
var environ_map = environ.createMap(gpa);
defer environ_map.deinit();

So, does it allocate?
If so, then how come that it didn’t accept an allocator parameter?
If not, then what is the significance of the part of the doc comment that says “initialized with gpa”?

Hi @Ahmad , welcome to Ziggit!

Juicy main does the deinit for you. So the ones passed into main will be initialized with GPA and should handle the deinit as well.

For the second example, because you are creating the map, you are in charge of deallocating. That’s why the deinit works fine.

2 Likes

The source of all this for me is that I didn’t pass an allocator parameter, but if I understand correctly this is the first exception to that rule.

A general rule of thumb is if you didn’t ever pass an allocator, then you don’t have to call deinit.

This is a special case of that rule. Juicy main did the allocation, so you don’t have to.
The special case is that since juicy main does the deallocation, you don’t have to worry about it, even if you call one of the apis and pass in an allocator.

‘deinit’ doesn’t always mean deallocation: it can be closing a database handle and similar resource release.

1 Like

True. I was talking more about allocation specifically. It’s good to call this out. Generally if you create the resource, you are incharge of calling deinit

Hidden control flow has crept into zig.

2 Likes

I’m not sure what you mean here.

Not if you go read start.zig :wink:

4 Likes

Then it will also be true for any programming language if your read the source code and documentation.
At this point hidden flow is not but an illusion

1 Like

I mean… no. Memory allocation is not control flow. Also, this is exactly the same as any other case where someone hands you some memory that you didn’t allocate— if you didn’t allocate it then you shouldn’t be responsible for freeing it.

At best this is a hidden dynamic allocation, since you have to know about the different process initialization options to control it, but in no way is this control flow.

4 Likes

OP’s initial code and argument (passing a gpa to the map) seemed very reasonable to me. The reason why it failed was hidden in non-trivial code which was nowhere in his source code or dependencies. That’s hidden control flow, or hidden code, whatever you want to call it.

1 Like

Since nobody has linked it, here’s the relevant part of start.zig:

https://codeberg.org/ziglang/zig/src/commit/764760df62c63bc2a3d4bd4ec95c000233625834/lib/std/start.zig#L697-L749

2 Likes

I forgot to check start.zig source code thank you for posting that.
But still one does not pass an allocator parameter to allocate that memory, but sense it happens in callMain() I think it’s less bad because what! you want to pass an allocator the main function!

Then, why even include it to begin with? what big problem does this exception to “In Zig if an API allocates memory then it must take an allocator” solve?

I personally will not use init.environ_map I think the following is just fine …

var environ_map = init.minimal.environ.createMap(gpa);
defer environ_map.deinit();

It turned out std.process.Init.environ_map is not the only thing that allocates memory in main, great!!!

that is literally what init.environ_map is, not only that, but your snippet is still using the full std.process.Init so init.environ_map still exists meaning your allocation is unecessary.

If you were to use std.process.Init.Minimal as the parameter it would be fine, but to ask for the full thing, then duplicate what it does with no changes is just un optimal.

6 Likes

Juicy Main is great to get things quickly, especially allocator and io. It’s a nice way to lower the barrier for newcomers to the language, as Hello World might have been frightening at first sight.

I never saw Juicy Main as something else than pure convenience to start with. But at some point in a project, I assume it has to be removed, at least to choose from Threaded/Evented io.

1 Like

Thank you for reminding me.
I actually thought about using Init.Minimal but I got lazy replacing init.io and init.gpa (which picks an appropriate allocator not just debug_allocator).

I don’t know if it’s just familiarity, but my knee-jerk reaction was, “why would you deinit() when you weren’t responsible for init()?” And I don’t even use juicy-main, and know very little about it! I think that what makes this “not hidden magic” is that symmetry.

What would be “wrong”, for sure, is if I called foo.init(gpa) (or create() or whatever) and was informed, in a back alley, that I should NOT call defer foo.deinit(), or otherwise consider my responsibility for the resource that required an allocator to create/init. That would be “bad magic” (which some might wrongly think “a nice convenience”, in the vein of GC). But juicy-main does feel like it’s supposed to be a batteries-included power-tool, so I’d enter into its use expecting it to do allocations, initializations, setups as well as the symmetric deallocations, deinitializations, and teardowns for me. I’d expect this with environ_map, io, and the whole kitten kaboodle (unless std.process.Init.Minimal was used instead of the full .Init, apparently, as @vulpesx pointed out, and about which I knew nothing about before).

2 Likes

That is besides the point I only included …

const environ_map = init.environ_map;
defer environ_map.deinit();

in my question as I was telling my thought process, I was telling the whole story that led to my confusion.
I don’t mean that should be the way.
What I do think should be the way is …

const environ_map = init.environ_map(gpa);
defer environ_map.deinit();

IMO, juicy main can still be batteries included with just, just, APIs that allocate memory to expect an allocator parameter, that is it.