The macro env!() is implemented via code in rustc itself, which reads rustc's environment. rustc probably relies on the fact that no code in rustc itself modifies the environment, but in any case, that is an implementation detail of rustc. Code you write for rustc to compile can't modify rustc’s environment, so it doesn’t matter whether the code being compiled has any calls to set_var(). (If you set an environment variable from a build script, that is not mutating the environment in-place — that's telling cargo what environment variables the future rustc child process should have. There can’t be a read-write race that way, because the writing is happening in a different process, before the process doing the reading exists.)
Thanks everybody for the productive discussion. Although I still consider it is a wrong move from the Rust community, I obey to the majority opinion and removed unsafe calls from my code. I replaced env::set_var calls to set calls to RwLock<Option<HashMap data structure. I was lucky enough to migrate the code from Java where the structure mimicked System.properties and actually env::set_var duplicated sets to the structure. So, somehow, my code is even cleanlier now.
To be clear, you can use unsafe code so long as you meet the safety requirements. It's just hard-to-impossible on a non-Windows multi threaded app.
You should be fine to configure the environment in main before calling to external code that may start threads, for example.
The POSIX setenv and getenv API is almost impossible for C programmers to use safely; it's frustrating that Rust pointing this out is somehow a Rust problem and not a POSIX one.
If you have a company policy to do not use unsafe blocks, then you can't argue. Generally, it's right. At the current time frame, you code can be absolutely safe even using unsafe blocks, however with the time, the situation can drastically changed, and your code in other hands or in other product shape can become unsafe. Simply being the Windows fan boy I always surprised how many issues has Linux.
As a general policy that is of course a good idea, and I didn't mean to imply that you should use unsafe if there's a safe way to do it instead, but I would raise an eyebrow at any absolute denial of any unsafe code ever.
There's several reasons why an API may be unsafe - perhaps because it's simply not possible to safely assert some fact external to Rust such as in FFI, but even in performance there should be a more nuanced policy than just "don't". If you have measured proof that the safe API adds significant overhead in your situation, and have encapsulated and documented the mechanism by which you are meeting the requirements of the unsafe API, then you're not helping anyone by being worse and, really the only concern should be people not at all reading the code they're changing, which is a much bigger concern than a keyword.
But hey, not my company.
Uh, yeah? You absolutely can. Anyway it seems strange that they trust strangers with unsafe (at the very least std contains large amounts of it, and probably other dependencies too), but not their own employees... Not a place I would want to work for.
And setenv is quite unsafe on POSIX (which is most OSes, basically everything except Windows). Go read the relevant issues. And you can still set env vars for child processes you spawn which covers almost all use cases.
This discussion here will not change anything in std, so it seems fairly pointless to continue.
Own employees are still humans, and humans still can make mistakes. Remember that. Even AI can make mistakes.
Yes, but the point was so are all the people writing the dependencies you use. Unless you also have a policy banning any unsafe in dependencies as well you're not magically any safer for using a third-party crate that wraps up some unsafe API than doing that yourself if you expect your developers to be at least about average (ie. you may have an argument if you think the average crate developer is better than your developers, but then the right fix is hire better developers!)
If you do have such a policy banning unsafe in third party code, or third party code at all, then it's likely (there are some cases where this can make sense) that you're instead wasting a huge amount of time rewriting code that works fine.
Again, not my company, though, so what do I know.
Company policy aside, I will note that I maintain a list of Unix-like operating systems that have thread-safe environments. Currently, this is illumos, NetBSD, and MacOS. Windows (while obviously not Unix-like) also has a thread-safe environment. If you gate a call behind a check to see if you're on any of these OS's, the call is guaranteed to be sound. You can also check if the current process is single-threaded, as I do for a fallback in this file.
I'm curious as to how that works. How can they support the global environ variable that POSIX mandates and be thread safe?
EDIT: After reading the Illumos implementation, some updates happens in place and doesn't use atomics. So setenv is MT-unsafe with C code reading directly from environ. So unless I missed something, that list is wrong.
EDIT 2: Since POSIX mandates extern char **environ;, any C code could also write via that. So no POSIX conforming OS can ever be safe as you claimed.
One of the core maintainers (jclulow) of illumos said it's thread-safe at some point in the past. I don't have a link off hand, but that assertion was enough for me.
Generally the company policy prohibits also using any 3rd party software. I think that most companies prohibit using externally developed software if they do some mission critical software as a nuclear reactor controller, or a space commander. But I agree that such companies are not the majority, and more likely you can ignore any safety rules.
Fair enough, though I feel "ignoring safety rules" is a bit unfair. You can perfectly safely use third party code and (despite the name) unsafe, it's just on you to verify that safety (just like any other code)
The major problem is the time. set_env call was safe a year ago. The same problem is with any
code. Indeed, it's safe now, but tomorrow? The God knows.
if you mean that it's bad when previously safe functions are changed to be unsafe, yes that's bad. But it's not bad because it is unsafe now, it's bad because it was always unsafe, just without being declared unsafe and thus the compiler couldn't warn you about it. These are bugs, and like any software, Rust tries hard to avoid those, but inevitably some sneak through.
I'm not aware of any other cases of previously safe APIs being made unsafe, though perhaps there are some - but it seems to be quite unusual.
I also want to raise that you seem to be mixing the concepts of "safe" and "sound" - the latter is whether the code is, roughly, valid Rust, while the former is about whether the compiler is able to prevent you from compiling unsound code - or in other words, that there are additional requirements. It's fine to mix the terms colloquially, but mixing the concepts together makes it easy to form a misunderstanding of the purpose of unsafe to consider the use of it someone "ignoring safety".
Do notice that this change was done across an edition. std::env::{set_var, remove_var} are only unsafe in 2024 edition. But you have to opt-in an edition, so this isn't a breaking change. You probably should upgrade edition and acknowledge in your code inherit unsafety of those functions. But notice that you aren't forced into doing that. And if in the future we realise that some other platform function std::os::frobnicate is unsafe, then changing it to unsafe fn will also happen across an edition.
A trivial rg deprecated_safe on the standard library reveals std::os::unix::process::CommandExt::before_exec, which was outright deprecated in favor of pre_exec. Its behavior is identical, just declared unsafe.
That's the only other example.