Structs are bare maps underneath
In the example above, pattern matching works because underneath structs are just bare maps with a fixed set of fields. As maps, structs store a “special” field named __struct__
that holds the name of the struct:
iex> is_map(john)
true
iex> john.__struct__
User
Notice that we referred to structs as bare maps because none of the protocols implemented for maps are available for structs. For example, you can’t enumerate nor access a struct:
iex> john = %User{}
%User{age: 27, name: "John"}
iex> john[:name]
** (UndefinedFunctionError) undefined function: User.fetch/2
iex> Enum.each john, fn({field, value}) -> IO.puts(value) end
** (Protocol.UndefinedError) protocol Enumerable not implemented for %User{age: 27, name: "John"}
However, since structs are just maps, they work with the functions from the Map
module:
iex> kurt = Map.put(%User{}, :name, "Kurt")
%User{age: 27, name: "Kurt"}
iex> Map.merge(kurt, %User{name: "Takashi"})
%User{age: 27, name: "Takashi"}
iex> Map.keys(john)
[:__struct__, :age, :name]
Structs alongside protocols provide one of the most important features for Elixir developers: data polymorphism. That’s what we will explore in the next chapter.