You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
We learned about maps [in earlier chapters](keywords-and-maps.md):
@@ -19,7 +20,7 @@ Structs are extensions built on top of maps that provide compile-time checks and
19
20
20
21
## Defining structs
21
22
22
-
To define a struct, the [`defstruct/1`](https://hexdocs.pm/elixir/Kernel.html#defstruct/1) construct is used:
23
+
To define a struct, the `defstruct/1` construct is used:
23
24
24
25
```elixir
25
26
iex>defmoduleUserdo
@@ -29,9 +30,7 @@ iex> defmodule User do
29
30
30
31
The keyword list used with `defstruct` defines what fields the struct will have along with their default values. Structs take the name of the module they're defined in. In the example above, we defined a struct named `User`.
31
32
32
-
## Creating structs
33
-
34
-
We can create `User` structs by using a syntax similar to the one used to create maps:
33
+
We can now create `User` structs by using a syntax similar to the one used to create maps:
35
34
36
35
```elixir
37
36
iex> %User{}
@@ -47,129 +46,26 @@ iex> %User{oops: :field}
47
46
** (KeyError) key :oopsnot found expanding struct:User.__struct__/1
48
47
```
49
48
50
-
You can also create structs using `Kernel.struct/2`:
51
-
52
-
```elixir
53
-
iex>struct(User)
54
-
%User{age:27, name:"John"}
55
-
iex>struct(User, name:"Jane")
56
-
%User{age:27, name:"Jane"}
57
-
iex>struct(User, %{name:"Jane", age:30})
58
-
%User{age:30, name:"Jane"}
59
-
```
60
-
61
-
### Default values and required keys
62
-
63
-
If you don't specify a default key value when defining a struct, `nil` will be assumed:
64
-
65
-
```elixir
66
-
iex>defmoduleProductdo
67
-
...>defstruct [:name]
68
-
...>end
69
-
iex> %Product{}
70
-
%Product{name:nil}
71
-
```
72
-
73
-
You can define a structure combining both fields with explicit default values, and implicit `nil` values. In this case you must first specify the fields which implicitly default to nil:
74
-
75
-
```elixir
76
-
iex>defmoduleUserdo
77
-
...>defstruct [:email, name:"John", age:27]
78
-
...>end
79
-
iex> %User{}
80
-
%User{age:27, email:nil, name:"John"}
81
-
```
82
-
83
-
Doing it in reverse order will raise a syntax error:
84
-
85
-
```elixir
86
-
iex>defmoduleUserdo
87
-
...>defstruct [name:"John", age:27, :email]
88
-
...>end
89
-
** (SyntaxError) iex:107: unexpected expression after keyword list. Keyword lists must always come last in lists and maps.
90
-
```
91
-
92
-
You can also enforce that certain keys have to be specified when creating the struct via the `@enforce_keys` module attribute:
93
-
94
-
```elixir
95
-
iex>defmoduleCardo
96
-
...>@enforce_keys [:make]
97
-
...>defstruct [:model, :make]
98
-
...>end
99
-
iex> %Car{}
100
-
** (ArgumentError) the following keys must also be given when building struct Car: [:make]
101
-
expanding struct:Car.__struct__/1
102
-
```
103
-
104
-
Enforcing keys provides 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.
105
-
106
-
## Accessing struct fields
49
+
## Accessing and updating structs
107
50
108
-
Structs support dot notation for accessing fields:
51
+
Structs share the same syntax for accessing and updating fields as maps of fixed keys:
109
52
110
53
```elixir
111
54
iex> john = %User{}
112
55
%User{age:27, name:"John"}
113
56
iex> john.name
114
57
"John"
115
-
iex> john.age
116
-
27
117
-
```
118
-
119
-
Unlike maps, structs do not support bracket notation access:
120
-
121
-
```elixir
122
-
iex> john[:name]
123
-
** (UndefinedFunctionError) function User.fetch/2 is undefined (User does not implement the Access behaviour)
124
-
User.fetch(%User{age:27, name:"John"}, :name)
125
-
```
126
-
127
-
## Updating fields
128
-
129
-
When you need to update specific fields with known values, use the update syntax:
The update syntax ensures that only existing struct fields can be updated:
141
-
142
-
```elixir
143
60
iex> %{jane |oops::field}
144
61
** (KeyError) key :oopsnot found in: %User{age:27, name:"Jane"}
145
62
```
146
63
147
64
When using the update syntax (`|`), Elixir is aware that no new keys will be added to the struct, allowing the maps underneath to share their structure in memory. In the example above, both `john` and `jane` share the same key structure in memory.
148
65
149
-
## Merging data
150
-
151
-
When you have data in maps or keyword lists that you want to merge into a struct, use `Kernel.struct/2`. It accepts maps, keyword lists, or any enumerable that emits key-value pairs, and automatically discards keys that are not defined in the struct:
Pattern matching structs is one of the most powerful features in Elixir. You can match on struct types, extract specific fields, and ensure data integrity all in one expression:
66
+
Structs can also be used in pattern matching, both for matching on the value of specific keys as well as for ensuring that the matching value is a struct of the same type as the matched value.
169
67
170
68
```elixir
171
-
iex> john = %User{name:"John", age:27}
172
-
%User{age:27, name:"John"}
173
69
iex> %User{name: name} = john
174
70
%User{age:27, name:"John"}
175
71
iex> name
@@ -178,57 +74,35 @@ iex> %User{} = %{}
178
74
** (MatchError) no match of right hand side value: %{}
179
75
```
180
76
181
-
Pattern matching structs is particularly useful in function heads to handle different data types:
77
+
For more details on creating, updating, and pattern matching structs, see the documentation for `%/2`.
182
78
183
-
```elixir
184
-
defmoduleNotificationdo
185
-
defsend(%ChatMessage{user: user, text: text}) do
186
-
"#{user}: #{text}"
187
-
end
188
-
189
-
defsend(%FriendRequest{sender: sender}) do
190
-
"#{sender} wants to be your friend"
191
-
end
192
-
193
-
defsend(%GameInvite{sender: sender, game: game}) do
194
-
"#{sender} invited you to play #{game}"
195
-
end
196
-
end
197
-
```
198
-
199
-
For more details on creating, updating, and pattern matching structs, see the documentation for [`Kernel.SpecialForms.%/2`](https://hexdocs.pm/elixir/Kernel.SpecialForms.html#%25/2).
79
+
## Dynamic struct updates
200
80
201
-
## Broken structs
202
-
203
-
Since structs are maps underneath, it's possible to change them using `Map` functions. However, this can "break" the struct by adding fields that were not defined in `defstruct`, turning it into a plain map:
81
+
When you need to update structs with data from variables or external sources, use `struct!/2`:
The result may look like a struct but it's actually a plain map with extra fields. This can lead to unexpected behavior when pattern matching or using protocols.
215
-
216
-
To safely merge data into structs, always use `Kernel.struct/2` instead of `Map` functions:
90
+
Unlike the update syntax, `struct!/2` accepts data from maps and keyword lists, and will raise an error if you try to set invalid fields:
217
91
218
92
```elixir
219
-
iex>john=%User{name:"John", age:27}
220
-
%User{age:27, name:"John"}
221
-
iex>struct(john, %{name:"Jane", email:"ignored"})
222
-
%User{age:27, name:"Jane"}
93
+
iex>fields=[name:"Jane", invalid:"field"]
94
+
[name:"Jane", invalid:"field"]
95
+
iex>struct!(john, fields)
96
+
** (KeyError) key :invalidnot found in: %User{age:27, name:"John"}
223
97
```
224
98
225
-
## Structs vs maps
99
+
Always use `struct!/2` instead of `Map` functions when working with structs, as functions like `Map.put/3` and `Map.merge/2` can break struct integrity. See the [`Kernel.struct!/2`](https://hexdocs.pm/elixir/Kernel.html#struct!/2) documentation for more details.
100
+
101
+
## Structs are bare maps underneath
226
102
227
103
Structs are simply maps with a "special" field named `__struct__` that holds the name of the struct:
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.
125
+
126
+
## Default values and required keys
127
+
128
+
If you don't specify a default key value when defining a struct, `nil` will be assumed:
129
+
130
+
```elixir
131
+
iex>defmoduleProductdo
132
+
...>defstruct [:name]
133
+
...>end
134
+
iex> %Product{}
135
+
%Product{name:nil}
136
+
```
137
+
138
+
You can define a structure combining both fields with explicit default values, and implicit `nil` values. In this case you must first specify the fields which implicitly default to `nil`:
139
+
140
+
```elixir
141
+
iex>defmoduleUserdo
142
+
...>defstruct [:email, name:"John", age:27]
143
+
...>end
144
+
iex> %User{}
145
+
%User{age:27, email:nil, name:"John"}
146
+
```
147
+
148
+
Doing it in reverse order will raise a syntax error:
149
+
150
+
```elixir
151
+
iex>defmoduleUserdo
152
+
...>defstruct [name:"John", age:27, :email]
153
+
...>end
154
+
** (SyntaxError) iex:107: unexpected expression after keyword list. Keyword lists must always come last in lists and maps.
155
+
```
156
+
157
+
You can also enforce that certain keys have to be specified when creating the struct via the `@enforce_keys` module attribute:
158
+
159
+
```elixir
160
+
iex>defmoduleCardo
161
+
...>@enforce_keys [:make]
162
+
...>defstruct [:model, :make]
163
+
...>end
164
+
iex> %Car{}
165
+
** (ArgumentError) the following keys must also be given when building struct Car: [:make]
166
+
expanding struct:Car.__struct__/1
167
+
```
168
+
169
+
Enforcing keys provides 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.
0 commit comments