Introducing the Struct
A struct is an extension of a map with more strict rules about what keys it can have. A struct’s keys are atoms and cannot be strings. Because of the strict definition of a struct, Elixir can provide compile-time checks for the keys. This won’t prevent you from adding invalid keys to the map, but it works very well for catching typos and when a key is renamed.
A struct is defined inside a module and the name of the struct is the module itself.
A simple struct:
defmodule Player do
defstruct [:username, :email, :score]
end
This defines a Player struct where the data has 3 named attributes. For a new struct, all the attributes will have the default value of nil
. When you copy/paste that module definition into IEx, you can then auto-complete on “Player.
” and it completes to Player.__struct__
. Execute that and you see the struct.
Player.__struct__
#=> %Player{email: nil, score: nil, username: nil}
The struct gets the name of the module. Since we didn’t define any default values for the struct, Elixir assigned nil
to each of the attributes. For a Player
, it would make sense that the score should default to 0 instead of nil
.
Contents
Default Values
When declaring the struct, we can provide a keyword list to provide default values. That looks like this:
defmodule Player do
defstruct username: nil, email: nil, score: 0
end
When creating a new Player struct, it defaults the score to 0 for us.
%Player{}
#=> %Player{email: nil, score: 0, username: nil}
Compile Time Checks on Keys
As mentioned before, Elixir can perform compile time checks for valid keys on the struct. This is an example of a runtime error for an invalid key.
%Player{lives: 100}
#=> ** (KeyError) key :lives not found
#=> expanding struct: Player.__struct__/1
A Struct is a Map
A struct is a map and can be accessed using normal map functions.
gary = %Player{username: "Gary", score: 100}
#=> %Player{email: nil, score: 100, username: "Gary"}
is_map(gary)
#=> true
Map.get(gary, :score)
#=> 100
gary.score
#=> 100
An Elixir struct is similar to a “class” in Object Oriented Programming languages. If you think about a class as the explicit linking of a data structure and the methods (or functions) that operate on that data, then a well-defined struct/module can do the same thing. The main difference for a struct is that the data structure and the functions are not explicitly tied together. We define them in the same place as a convenience both to the developer creating the data structure and writing the tests, but also to the developer using the struct. The primary functions for operating on the struct are located in the same namespace.
No Default Access Behaviour
A struct does not implement the Access Behaviour mentioned when talking about Maps. That behaviour (yes, spelled the British English way) allows you to use []
to provide a key to access a value in a map. When you try that on a struct it fails.
gary[:username]
#=> ** (UndefinedFunctionError) function Player.fetch/2 is undefined (Player does not implement the Access behaviour)
#=> Player.fetch(%Player{email: nil, score: 100, username: "Gary"}, :username)
#=> (elixir) lib/access.ex:322: Access.get/3
gary.username
#=> "Gary"
Structs have pre-defined atom keys. You can use the key name like gary.username
and it returns the value.
There’s Much More to Structs
This is only an introduction to Elixir structs so we have enough of a foundation to use them in pattern matching. The combination of structs and pattern matching are powerful and beneficial. They are two features that go great together.
For more information on structs, they are covered in more detail in the Elixir Fundamentals Collection. You can also find information online.
4 Comments
Comments are closed on this static version of the site.
Comments are closed
This is a static version of the site. Comments are not available.