> and environment variables require an operating system
Is that true? It's just a process global string -> string map, that can be pre-loaded with values before the process starts, with a copy of the current state being passed to any sub-process. This could be trivially implemented with batch processing/supervisory programs.
Sure, there's a broader concept here, which doesn't require any operating system. But any alternate string->string map you define won't answer to C code calling getenv, won't be passed to child processes created with fork, won't be visible through /proc/$PID/environ, etc.
> They did, it's called core. But it assumes no operating system at all, and environment variables require an operating system.
I think there's some confusion here. The C standard library is an abstraction layer that exists to implement standard behavior on hardware. It's entirely unrelated to the existence of an OS. Things like "/proc/$PID/environ" have nothing to do with C.
There are many standard libraries, for embedded, that implement these things, like getenv, on bare metal [1].
Standard C libraries exist to implement functionality. It does not define how to implement the functionality. That's the whole point of C: it's an abstraction that has very little requirements.
The implementation of environment variables don't require an OS. If they made this "core", they could trivially implement the concept.
I think newlib requires a discussion of its own, and more generally, the concept of a "full" libc outside of a formal operating system.
To put it bluntly, newlib is an antisocial libc. It provides bare compileability of programs by implementing C and POSIX facilities atop a small set of system calls. However, in practice, it requires basically nothing to actually work. If you look at what it requires [1], you can see that virtually all of the system calls are allowed to do nothing but return an error. The only function that is actually shown to do something is sbrk which is a simple bump allocator, and even then it's only strongly recommended to work so that malloc also works since a lot of ordinary C programs use malloc. This says to me "get code to compile at all costs" with no concern for a wider environment (since there may be no "wider environment" in the first place).
More charitably, we can view newlib as a set of compatibility shims bridging hosted and freestanding C. This has a place, of course; there are C libraries that assume a hosted implementation but don't really need (all of) a hosted implementation.
This doesn't really apply to nostd Rust, and creating a set of "environment variables" that interoperate with nothing, just because you can, is kind of pointless when there's no O/S and no FFI involved. I explained more about why (IMO) core::env/alloc::env shouldn't exist in the other comment.
All that having been said, newlib does seem to sit in a position somewhere between core+alloc and full std in terms of Rust (std also includes networking). Maybe there is a need for FFI/C compatibility without networking? I can't say for sure, but I haven't needed it.
I don't think I'm confused, but let's recapitulate the thread history as I understand it:
Context: The setenv function is not thread-safe even in Rust
Question: Why doesn't Rust implement a standard library without C?
Answer: It does, but core lacks std::env, because env vars are part of an O/S
Question: Is an O/S really necessary for env vars?
Answer: Not conceptually, but without an O/S, env vars don't work as expected
I also like the sibling comment that pointed out env vars are social as much as technical. The key element is interoperability. And we haven't even discussed Windows, which has different functions and conventions for environment variables.
Now, let me address what you just said. First of all, on embedded, a freestanding C implementation is not even required to provide getenv at all. Second, while getenv is in standard C and required for hosted implementations, setenv is not. And the whole thread is really about setenv. Once we pull in setenv, we're talking not just about standard C but about POSIX, which is a specification for operating systems. I assume for the sake of fruitful discussion, we both accept that a variable put into the environment with setenv should be retrievable thereafter with getenv. This moreover should apply even if it's Rust that calls setenv and C that calls getenv and vice-versa.
So, however Rust implements environment variables should be consistent with how C implements environment variables, and since C provides the foundation for system calls and FFI for most other major languages, adhering to this convention allows interoperability across very many languages. This convention is defined by libc (the implementation of the C standard and POSIX interfaces) and thus interoperability is based on libc compatibility. So either Rust implements its own libc, which C programs would have to be (re-)compiled to use, or else it uses an existing implementation of libc, inheriting all of its quirks. Indeed, Rust targets specify the libc (or equivalent) they're using, such as -gnu, -musl, -darwin, -mingw, -msvc, etc. Linking with libraries built for a different libc on an otherwise identical platform (-gnu vs. -musl on Linux, -mingw vs -msvc on Windows) generally doesn't work and even when it appears to work leads to strange issues later. So you can't just write your own getenv and expect it to work with some other implementation of setenv.
To connect back with my other comments, there is no core::env because core assumes no libc at all. The nostd flavor of Rust (where core is available but not std) is basically equivalent to freestanding C and like freestanding C there is no interoperability guarantee, not even with freestanding C on the same hardware (indeed, the whole concept of "freestanding" is that there are no conventions to adhere to in the first place). So, std::env::set_env has the exact same problems as C setenv because it's the same thing under the hood. This cannot be addressed without fixing libc itself. Moreover, when libc is not involved, then there is no env to support to begin with.
Finally, to round out addressing what you said, core::env could exist, but probably shouldn't, for two reasons. First, it would be misleading. As I've already laid out, it would not interoperate with anything since there's nothing there to interoperate with. It would just be a global string->string map exclusive to that program, which the programmer could just as well create on his own. Second, because presumably you want it to be something other than empty, it would require some kind of global allocator, which core also assumes doesn't exist. So it would have to be something like alloc::env instead, and once you've pulled in alloc, you can just use one of the collection types (though, notably, HashMap isn't in alloc yet [1]).
Well, it's used by the OS when exec-ing a new process, but at least the Linux syscall for that takes the environment as an explicit parameter. So it could be managed in whatever way by the runtime until execve() is called.
Is that true? It's just a process global string -> string map, that can be pre-loaded with values before the process starts, with a copy of the current state being passed to any sub-process. This could be trivially implemented with batch processing/supervisory programs.