Kernel.defstruct
defstruct
, go back to Kernel module for more information.
Defines a struct.
A struct is a tagged map that allows developers to provide default values for keys, tags to be used in polymorphic dispatches and compile time assertions.
To define a struct, a developer must define both __struct__/0
and
__struct__/1
functions. defstruct/1
is a convenience macro which
defines such functions with some conveniences.
For more information about structs, please check Kernel.SpecialForms.%/2
.
Examples
defmodule User do
defstruct name: nil, age: nil
end
Struct fields are evaluated at compile-time, which allows
them to be dynamic. In the example below, 10 + 11
is
evaluated at compile-time and the age field is stored
with value 21
:
defmodule User do
defstruct name: nil, age: 10 + 11
end
The fields
argument is usually a keyword list with field names
as atom keys and default values as corresponding values. defstruct/1
also supports a list of atoms as its argument: in that case, the atoms
in the list will be used as the struct's field names and they will all
default to nil
.
defmodule Post do
defstruct [:title, :content, :author]
end
Deriving
Although structs are maps, by default structs do not implement
any of the protocols implemented for maps. For example, attempting
to use a protocol with the User
struct leads to an error:
john = %User{name: "John"}
MyProtocol.call(john)
** (Protocol.UndefinedError) protocol MyProtocol not implemented for %User{...}
defstruct/1
, however, allows protocol implementations to be
derived. This can be done by defining a @derive
attribute as a
list before invoking defstruct/1
:
defmodule User do
@derive [MyProtocol]
defstruct name: nil, age: 10 + 11
end
MyProtocol.call(john) # it works!
For each protocol in the @derive
list, Elixir will assert the protocol has
been implemented for Any
. If the Any
implementation defines a
__deriving__/3
callback, the callback will be invoked and it should define
the implementation module. Otherwise an implementation that simply points to
the Any
implementation is automatically derived. For more information on
the __deriving__/3
callback, see Protocol.derive/3
.
Enforcing keys
When building a struct, Elixir will automatically guarantee all keys belongs to the struct:
%User{name: "john", unknown: :key}
** (KeyError) key :unknown not found in: %User{age: 21, name: nil}
Elixir also allows developers to enforce certain keys must always be given when building the struct:
defmodule User do
@enforce_keys [:name]
defstruct name: nil, age: 10 + 11
end
Now trying to build a struct without the name key will fail:
%User{age: 21}
** (ArgumentError) the following keys must also be given when building struct User: [:name]
Keep in mind @enforce_keys
is a simple compile-time guarantee
to aid developers when building structs. It is not enforced on
updates and it does not provide any sort of value-validation.
Types
It is recommended to define types for structs. By convention such type
is called t
. To define a struct inside a type, the struct literal syntax
is used:
defmodule User do
defstruct name: "John", age: 25
@type t :: %__MODULE__{name: String.t(), age: non_neg_integer}
end
It is recommended to only use the struct syntax when defining the struct's
type. When referring to another struct it's better to use User.t
instead of
%User{}
.
The types of the struct fields that are not included in %User{}
default to
term()
(see term/0
).
Structs whose internal structure is private to the local module (pattern
matching them or directly accessing their fields should not be allowed) should
use the @opaque
attribute. Structs whose internal structure is public should
use @type
.