How do I access Environ from a dynamically loaded library (.so)?

comptime {
    @export(&NapiRegistrationEntry, .{ .name = "napi_register_module_v1", .linkage = .strong });
}

// This is my entry point.
// It is invoked by Node.js during addon instantiation.
// It can't have the "init" parameter with Environ:
fn NapiRegistrationEntry(env: napi.napi_env, exports: napi.napi_value) callconv(.c) napi.napi_value {
  // ?? How do I access Environ here?
}

It appears that std.process.getEnvMap/getEnvVarOwned have vanished in 0.16?

Link to libc and use the same global environment that you would in C.

Not going to work for me because I need to feed it into the new `std.process.spawn`, which requires that I pass a pointer to Environ.Map.

On POSIX/linked with libc, zigs environ implementation works over the same data that libc does. You can just create a zig environ from libc’s

extern "c" const environ: [*:null]const ?[*:0]const u8;

pub fn main(init: std.process.Init) !void {
    {
        var map = try init.minimal.environ.createMap(init.gpa);
        defer map.deinit();
        var iter = map.iterator();
        std.debug.print("zig\n", .{});
        while (iter.next()) |e| {
            std.debug.print("{s}={s}\n", .{ e.key_ptr.*, e.value_ptr.* });
        }
    }
    {
        const env: std.process.Environ = .{ .block = .{ .slice = std.mem.sliceTo(environ, null) } };
        var map = try env.createMap(init.gpa);
        defer map.deinit();
        var iter = map.iterator();
        std.debug.print("c\n", .{});
        while (iter.next()) |e| {
            std.debug.print("{s}={s}\n", .{ e.key_ptr.*, e.value_ptr.* });
        }
    }
}
5 Likes

Thank you, that’s indeed a working workaround for POSIX! And for Windows I’ll have to re-do what Environ already does on Windows? What’s the point then of even having Zig’s std if it cannot be used? Let’s just ship bindings for libc and be done with it.

It worked just fine in 15.2, one release ago.

Windows is even easier since it being global is a windows side thing, unlike POSIX. std.process.Environ{ .block = .global }.

you can use zig std, It’s just not as easy. That is intentional!
Being able to access the environment globally is hidden control flow and can introduce foot guns.

On master, zig finally implemented “juicy main”. That is, you can ask for a bunch of goodies as a parameter to main instead of setting them up yourself (see the code I sent earlier).

zig took that opportunity to make environment variables and process args have a non-global API (may still be global behind it ofc) and favour passing them around like you already do with allocators and (on master) Io. And ofc you can easily get them via juicy main, but not so easily anywhere else.

The idea being you should just take a parameter/field instead of using the environment, if the caller wants to get the value from the environment they can do that themselves.


why do you need zigs environ when napi provides its own?

I should mention that there is Environ.empty if you need one but dont care about the actual values.

1 Like

No, sir! Cloning the globally accessible environment object with the purpose of modifying it in order to spawn a child process does not constitute a hidden control flow. It’s just an excuse to ship deficient API, and such attitude will force everyone to use libc instead of Zig’s std in real-world situations. std is supposed to be a reliable toolbox for all vital scenarios. Right now it’s not.

I do appreciate your answer and readiness to defend Zig’s design philosophy. This particular one is not worth defending though.

1 Like

But changing the behaviour based on the presence, absence or value of an environment variable is hidden control flow.

For an entire program it’s fine and expected, but for a function, let alone a library, it should be configured by more explicit means.

If I want it to be based on an environment variable I will do that myself.

Most zig projects are libraries or applications, they should be abiding by my above explanation.

I absolutely agree that they should better support your use case, but I also don’t think it’s anywhere close to being unusable! It took me about 5 minutes in total to figure out how to get an Environ instance for each platform.

I understand that I am marginally more familiar with the juicy main, environ and args changes, and that it’s difficult on master where things change every day.

I encourage you to make an issue about the deficiency of this API for your use case, it won’t get better without feedback.


A common, and justified, misunderstanding is currently the std library is a toolbox for the compiler, the vast majority of it exists because the compiler needs/needed it.

Some other things are added, including important ecosystem abstractions like Allocator, Reader, Writer and Io. those are exceptions

It is made in a way to be used outside of the compiler, and things are kept after they are no longer used in the compiler as they are useful to others.

But in a language that is not stable, with a compiler that is not stable, you should not expect a stable and reliable toolbox of a std library.

1 Like

I think this is not a justified expansion of the concept of hidden control flow. ENV variables aren’t hidden, they’re global.

Global state is notoriously difficult to reason about, there are plenty of ENV-specific footguns, but neither the existence of the environment, nor the fact of a function examining it and acting accordingly, is hidden.

Zig exists to compile Zig, that’s a straightforward consequence of how development is organized. This is called compiler-driven development. It’s a known antipattern and things are proceeding predictably as a result.

3 Likes

Global environment is a libc concept. It does not exist in many OSes actually. Thats why if you compiled zig library without linking to libc, accesing env would most likely sigsegv or worse.

Libc has lots of these small global states that cause issues eventually, especially when multi-threading is mixed in.

Imo i would design your library so that it doesnt depend on this global environ. But if you really need to, then indeed you need to bite the bullet and depend on libc and use its interface.

3 Likes

And, also, why would you want to always parse it on startup? I bet that majority of Zig applications will never even bother with env variables. Yet, all of them, and I currently have over 50 on my debian box, will be parsed (sentinel 0 lookups), allocated, organized into a hashmap. If you run 100 tests, it will allocate, and parse all 50 env variables 100 times, I kid you not. What a waste of memory and resources. We really ought to expose them on demand, as an immutable map, and under the std.once() pattern.

  1. std.once is no more
  2. You’re free not to accept any parameters to main, in which case nothing is parsed for you.
1 Like
  1. No, you can’t disable it for unit tests. Those will always parse envs, whether you like it or not. And you most definitely don’t need those for 99% of unit tests.
  2. If I don’t accept any parameters in main, I don’t get io, right? Although that one is probably easy to construct manually.

It doesn’t parse the environment until you ask it to, Environ is just a wrapper over the raw data, or a bool to be able to have an “empty” map in spite of the global implementations.

You don’t get a hash map until you call environ.createMap(allocator)

Even the old global API didn’t parse it at startup either.

I’m going to hard disagree with this, to me it seems you’re more upset with the fact you’ve come to learn how to do it one way and that Way is changing.

A quick google search of:

“compiler driven development is an antipattern”

Yields no results, I think it would be nice to have this elaborated on as it doesn’t seem to be as known as you have stated it to be.

More like disappearing. I used to be able to do it with just std.* API. Now I’ll have to resort to libc. Quite some progress, isn’t it?

It all boils down to the question who is the end consumer of the std library. Is it Zig compiler and infra only, and all unused classes should be thrown away. Or it’s a standard library for projects that are written in Zig. In this case, the functionality should be improved, but not cut out.

I do agree with serialization drop, that’s should indeed be tailored to specific situations. But dropping Mutex.Recursive, or environment variables? Mutex.Recursive required some minor update, replacing ThreadID with FiberID if the Io is evented.

That’s alright, I guess. I’ll just roll my own.