RFC: Immutable ENV - #19
Conversation
|
No strong opinion, but I do like the Go approach. |
|
I'm personally leaning on the startup snapshot (Java / Swift / Go) + explicitly unsafe method to set a variable (Rust). We're safe by default. We encourage to never set a variable, but developers can choose to be unsafe at their own perils (you've been warned). |
|
I think the calculus is a bit different when we currently allow |
|
I think it might be confusing if Crystal's So ultimately, I'd prefer not being able to mutate the snapshot independently of |
|
|
||
| Still, a number of programs are gonna be affected because they mutate `ENV`, | ||
| including the Crystal's spec suites! Here are the most common cases we | ||
| identified: |
There was a problem hiding this comment.
thought: We're missing the use case of mutating ENV["PATH"] for looking up executable paths.
A workaround is to explicitly resolve the path with Process.executable_path. But that's a bit bulky.
Maybe we could consider adding a path parameter to Process.run?
# old
ENV["PATH"] = "#{mypath};#{ENV["PATH"]}"
Process.run("foo")
# workaround
Process.run(Process.executable_path("foo", mypath))
# alternative?
Process.run("foo", path: mypath)There was a problem hiding this comment.
Process.run already has far too much params 🙈
There was a problem hiding this comment.
Let's make a process builder then... 🚀
(only half-joking here; it could be a useful pattern and many other languages have this)
There was a problem hiding this comment.
Also Process.executable_path isn't exactly a replacement for how CreateProcessW searches executables on Windows because the former only searches in PATH, while the latter searches a bunch of places before PATH.
We might consider fixing Process.executable_path on Windows.
| argument to docker and podman, ... or develop alternatives to not depend on | ||
| `ENV` only. |
There was a problem hiding this comment.
suggestion: I would move up alternatives to ENV as primary recommendation. ENV is great for ingesting external configuration. But when you load configuration data directly in the process, there are much better alternatives.
For example, class variables are type safe and can be easily documented.
If you really need it, you can store free-form key-value mappings in a custom Hash(String, String), accessible as a constant.
More complex configuration libraries can help as well.
Prepping the environment before executing the application can work well, but it's a deployment problem, not an engineering solution.
| > [!NOTE] | ||
| > We can't recommend enough a configuration library such as [totem] to revisit | ||
| > and centralize your application's settings. |
There was a problem hiding this comment.
thought: I'm not sure about such a strong endorsement for a specific library.
I personally have no experience with totem. Maybe it is that good?
It doesn't seem to be used a lot, though.
Co-authored-by: Johannes Müller <straightshoota@gmail.com>
I agree, but my understanding was that the Go scheme would take a read snapshot at the start, and any mutations would apply to both the snapshot and the system env vars. However I did overlook that a C library writing to env would not be picked up by Go, only the other way around. |
|
Johannes point was that mutating the environment in Crystal shall make it visible to external libraries because we mutated the environment. An external C library calling I believe us three are aligned on the following: we want the initial snapshot of The topics we're not necessarily aligned are:
I believe deprecating will bring awareness to the problem (there's no @[Unsafe] annotation). We can delay a possible Probably expand on the Environment variables are meant to externally configure a program. We can explictly pass a modified copy of the environment when spawning a subprocess. sentences of the "Motivation" section, with how to read |
|
deprecation + unsafe_set sounds good to me |
|
Before settling on anything final, we should do a bit more exploration to better understand the cases where there might be a need for environment modification. Deprecating the existing methods would help to raise awareness and identify affected code. But then it's not great to issue deprecation warnings without clarity on how a replacement might look like. We should just start with grepping through source code to identify I've already began some identifications, and I've deployed on a yak shaving tour with |
|
I checked my own projects: I already only read from For example instead of The few cases where I write to For example tests for methods that use ENV.mock({ "KEY" => "value" }) do
ENV["KEY"]?.should eq("value")
endOther cases are to cleanup # foo.cr
module Foo
class_properties(value) { ENV["DEFAULT"]? }
end
# spec/spec_helper.cr
require "foo"
Foo.value = "explicit"
# spec:
Foo.value.should eq("explicit")Edit: and these examples should be in the guide level section. |
Preview:
https://github.com/crystal-lang/rfcs/blob/rfc-immutable-env/text/0019-immutable-env.md
Related to:
ENVcrystal#16449ENV+ unsafe get/set + safe spec mock + optimizations crystal#16567Crystal::System::EnvwithSync::RWLock(UNIX) crystal#16591ENV.unsafe_setand deprecate mutable methods onENVcrystal#16601