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.