Structs and Protocols
Elixir structs for typed data and protocols for polymorphism
Structs
Structs are compile-time maps with a defined set of fields and a __struct__ key. They provide a contract for data shape.
Defining a struct
defmodule User do
defstruct name: nil, email: nil, age: 0
endCreating struct instances
# All fields
u = %User{name: "Alice", email: "[email protected]", age: 30}
# With defaults
u = %User{name: "Alice"}
# Access fields
u.name # => "Alice"
# Update fields (returns new struct)
%{u | age: 31} # => %User{name: "Alice", email: nil, age: 31}
# Pattern matching
%User{name: name} = u
name # => "Alice"Enforcing keys
Use @enforce_keys to require certain fields at creation time:
defmodule Order do
@enforce_keys [:id, :total]
defstruct [:id, :total, status: :pending]
end
%Order{id: 1, total: 99.99} # OK
%Order{total: 99.99} # ** (ArgumentError) the following keys must also be given when building struct Order: [:id]Deriving protocols
defmodule Point do
@derive [Inspect, {Poison.Encoder, only: [:x, :y]}]
defstruct [:x, :y]
endProtocols
Protocols provide polymorphism in Elixir — dispatching behavior based on the type of the first argument.
Defining a protocol
defprotocol Size do
@doc "Calculates the size of a data structure"
def size(data)
endImplementing for types
defimpl Size, for: BitString do
def size(string), do: String.length(string)
end
defimpl Size, for: List do
def size(list), do: length(list)
end
defimpl Size, for: Map do
def size(map), do: map_size(map)
end
# Using it
Size.size("hello") # => 5
Size.size([1, 2, 3]) # => 3
Size.size(%{a: 1, b: 2}) # => 2Implementing for a struct
defmodule Box do
defstruct [:width, :height]
end
defimpl Size, for: Box do
def size(%Box{width: w, height: h}), do: w * h
end
Size.size(%Box{width: 10, height: 5}) # => 50Fallback with @fallback_to_any
defprotocol Blank do
@fallback_to_any true
def blank?(data)
end
defimpl Blank, for: List do
def blank?([]), do: true
def blank?(_), do: false
end
defimpl Blank, for: Any do
def blank?(_), do: false
end
Blank.blank?([]) # => true
Blank.blank?([1]) # => false
Blank.blank?("hello") # => false (falls back to Any)Built-in protocols
| Protocol | Purpose | Example |
|---|---|---|
String.Chars |
Convert to string | to_string/1 |
Inspect |
Custom pretty-printing | inspect/1 |
Enumerable |
Iterate over collections | Enum.map/2 |
Collectable |
Collect into a container | Enum.into/2 |
Enumerable + Collectable |
Enables for comprehensions |
— |
Deriving built-in protocols
defmodule User do
@derive {Inspect, only: [:name]}
defstruct [:name, :email, :password]
end
inspect(%User{name: "Alice", email: "[email protected]", password: "secret"})
# => %User{name: "Alice", ...}