TIL: Weak optimization of System.get_env/0 in Elixir

Each day brings new opportunities and challenges. In the series “Today I Learned” (TIL), I wanted to cover topics that may not be widely known. Let me know in the comments what you think about such a series. Here is the first such topic.

Based on Erlang VM, Elixir is often limited by a virtual machine’s capabilities or the language itself. One such limitation manifests itself in weak optimization of System.get_env/01.

1
2
3
4
5
6
7
8
  @spec get_env() :: %{optional(String.t()) => String.t()}
  def get_env do
    Enum.into(:os.getenv(), %{}, fn var ->
      var = IO.chardata_to_string(var)
      [k, v] = String.split(var, "=", parts: 2)
      {k, v}
    end)
  end

Breaking the string into [key, value] is necessary because from :os.getenv()2 we get concatenated information instead of divided. See that $= in line 3.

1
2
3
  -spec getenv() -> [env_var_name_value()].
  getenv() ->
      [lists:flatten([Key, $=, Value]) || {Key, Value} <- os:list_env_vars() ].

Impact

I have never used this feature. Instead, I used System.get_env/13, which is much better prepared and does not need to operate on the entire environment variables list.

The impact of this bug is relatively low. If we have to use these variables in runtime, a better solution seems to be System.get_env/1 or the use of the cache mechanism.

Source

This article was based on information from Trevor Brown and his blog. You can find the source article in which the author explains the background of the changes here: Performance of Elixir’s System.get_env/0 Function.

  1. System.get_env/0 in Elixir v11.0 

  2. Erlang :os.getenv() in OTP 23.0 

  3. System.get_env/1 in Elixir v11.0