The same value - a compiled constant - will be used each time. We can see this is indeed the case by inspecting the BEAM bytecode.
Let’s prepare an example module:
defmodule Test do
def foo, do: %{foo: "bar"}
end
Save it as test.ex
, compile with elixirc test.ex
- the output will be the binary Elixir.Test.beam
. We can disassemble the compiled module into the BEAM assembly with :beam_disasm.file/1
. The output contains the section that interests us the most - the foo/0
function:
{:function, :foo, 0, 7,
[{:line, 1},
{:label, 6},
{:func_info, {:atom, Test}, {:atom, :foo}, 0},
{:label, 7},
{:move, {:literal, %{foo: "bar"}}, {:x, 0}},
:return]
}
We have a function foo/0
that starts at label 7. The whole body of the function consists of moving a literal value to the X0 register (which is the return register on the BEAM - the function calling that one will expect the result in this exact location) and returning from the function. So we see a literal value is used.
But what does it mean “a literal value”? Each .beam
file consists of several “chunks” that represent various things - the compiled code itself (Code
chunk), public (exported) functions (ExpT
), static atoms used in that module (Atom
), etc. One of those chunks is the literals chunk called LitT
. When the module is loaded, the chunk is unpacked and all the terms in there are placed somewhere in memory - they are constants that can be referenced multiple times (and since we know they don’t change and won’t go away they are skipped in garbage collection).
With the help of a function like below we can list all the literals from the chunk:
defmodule Literals do
def literals(module) when is_atom(module) do
path = :code.which(module)
{:ok, {^module, [{'LitT', <<_::4*8, compressed::binary>>}]}} =
:beam_lib.chunks(path, ['LitT'])
<<_::4*8, records::binary>> = :zlib.uncompress(compressed)
unpack(records)
end
defp unpack(<<>>) do
[]
end
defp unpack(<<len::4*8, record::binary-size(len), rest::binary>>) do
[:erlang.binary_to_term(record) | unpack(rest)]
end
end
For our Test
module, Literals.literals(Test)
gives [[foo: 0], %{foo: "bar"}]
- which is the list of exported functions (used in the __info__(:functions)
call) and our literal map.