Here some more use cases that are annoying to support well without a separate rust-private environment which with safe reads/writes (note that I fully admit that there are probably non-trivial details that need to be worked out WRT how this should work).
Note that I'm assuming that we cannot rely on the proposed thread-safe env API for quite some time — I can't see how we could rely on it given that it does not yet exist, and we want to support a wide range of OS versions (we also can't reasonably have an API that's only safe on some OS versions, especially when in practice that gets determined at runtime).
Sorry in advance for writing so much, if you don't care about the details, I get to the point at the end.
Configuring a process spawned by a library
If call a library function, which does some thinking before invoking a process via
Command. This is very handy, especially in code that orchestrates builds (such as build.rs's, the xtask pattern, ...)
That is, at the moment (unless the library explicitly prevents it) I can configure the spawned process's environment by writing to the env prior to calling whatever function of the library that spawns the process.
Of course, this isn't completely ideal, but it has a significant benefit: it more-or-less just works without any additional effort required by the library developer — they don't have exposed functionality for configuring the
Command they're going to spawn.
Without some safe and sound way for Rust code to write to the env in a way that is visible to Command (and ideally other rust code too), we'll either lose this ability or every library that uses
Command in some fashion would essentially have to grow support for configuring it.
Environment configuration from files
dotenv crate finds and parses
.env files (generally shortly after startup), and populates the global environment with the values it reads. These files generally are used to store sensitive data, and are kept out of version control.
It's considered best practice to put this type of data in the environment, to the point where taking it as parameters tends to be strongly discouraged outside of test code. I believe, the logic here is: if the SDK for some API accepts sensitive data as a function parameter, it might encourage naive developers to hardcode this information in the source. However, if it forces them to pass in sensitive info via the environment, then the path of least resistance becomes to do the right thing.
dotenv usage will probably happen very near startup, it could easily be after threads are spawned, such as in
#[tokio::main] (and async is common in webapps, the place where this pattern is the most common)... and while
dotenv doesn't do this, it's not hard to imagine similar code which uses async IO (or even just
spawn_blocking) to read the files.
Perhaps in an ideal world, populating the environment would happen prior to process launch, but this can be hard to orchestrate (especially across multiple prod/dev/test/...), and so things like this are not uncommon.
More generally, I believe the Rust-writer/Rust-reader use-case is way too widespread to ignore, and often will not have a straightforward migration path.
The Rust/C use case is of course important too, but... it's probably unfixable without making
setenv and friends fully thread-safe (via something like what @zackw proposed). While that'd be great, it seems like we won't be able to rely on it for a long time.
However, if we have two environments (one being Rust-private and safe to read/write, and the other being the C environment, which is unsafe to write to), we solve several problems:
Rust/Rust usage has a clear migration path to a safe API, even if they have no control over thread usage used by the application.
Without this, I worry a lot of code will either
#[allow(deprecated)] or use the
unsafe function without correctly verifying usage is single-threaded, which seems very hard to verify.
Writes to the Rust env cannot cause memory safety problems for C code using
getenv. Of course, C code won't see the write, which may be surprising (hopefully less so if we name the APIs well).
While this is a little unfortunate, the upshot is that FFI libraries can continue to be sound, even if the C code it binds to has a
getenv somewhere, which is... a bigger concern of mine than it should be.
Writes to the env from Rust that do need to be visible to C (for example, at the start of main and such) can still be done, it just requires calling an
And of course, I'm not proposing changing the existing API beyond deprecation, so existing working code won't break.