Skip to content

Commit 6fd2181

Browse files
authored
Update structs.md
1 parent 409beba commit 6fd2181

File tree

1 file changed

+64
-145
lines changed

1 file changed

+64
-145
lines changed

lib/elixir/pages/getting-started/structs.md

Lines changed: 64 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
SPDX-License-Identifier: Apache-2.0
33
SPDX-FileCopyrightText: 2021 The Elixir Team
44
-->
5+
56
# Structs
67

78
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
1920

2021
## Defining structs
2122

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:
2324

2425
```elixir
2526
iex> defmodule User do
@@ -29,9 +30,7 @@ iex> defmodule User do
2930

3031
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`.
3132

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:
3534

3635
```elixir
3736
iex> %User{}
@@ -47,129 +46,26 @@ iex> %User{oops: :field}
4746
** (KeyError) key :oops not found expanding struct: User.__struct__/1
4847
```
4948

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> defmodule Product do
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> defmodule User do
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> defmodule User do
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> defmodule Car do
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
10750

108-
Structs support dot notation for accessing fields:
51+
Structs share the same syntax for accessing and updating fields as maps of fixed keys:
10952

11053
```elixir
11154
iex> john = %User{}
11255
%User{age: 27, name: "John"}
11356
iex> john.name
11457
"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:
130-
131-
```elixir
132-
iex> john = %User{name: "John", age: 27}
133-
%User{age: 27, name: "John"}
13458
iex> jane = %{john | name: "Jane"}
13559
%User{age: 27, name: "Jane"}
136-
iex> older_jane = %{jane | age: 30, name: "Jane Smith"}
137-
%User{age: 30, name: "Jane Smith"}
138-
```
139-
140-
The update syntax ensures that only existing struct fields can be updated:
141-
142-
```elixir
14360
iex> %{jane | oops: :field}
14461
** (KeyError) key :oops not found in: %User{age: 27, name: "Jane"}
14562
```
14663

14764
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.
14865

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:
152-
153-
```elixir
154-
iex> john = %User{name: "John", age: 27}
155-
%User{age: 27, name: "John"}
156-
iex> fields = [name: "Jane", age: 30]
157-
[name: "Jane", age: 30]
158-
iex> struct(john, fields)
159-
%User{age: 30, name: "Jane"}
160-
iex> struct(john, %{name: "Jane", invalid_key: "ignored"})
161-
%User{age: 27, name: "Jane"}
162-
iex> struct(john, [name: "Jane", another_invalid: "also ignored"])
163-
%User{age: 27, name: "Jane"}
164-
```
165-
166-
## Pattern matching structs
167-
168-
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.
16967

17068
```elixir
171-
iex> john = %User{name: "John", age: 27}
172-
%User{age: 27, name: "John"}
17369
iex> %User{name: name} = john
17470
%User{age: 27, name: "John"}
17571
iex> name
@@ -178,57 +74,35 @@ iex> %User{} = %{}
17874
** (MatchError) no match of right hand side value: %{}
17975
```
18076

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`.
18278

183-
```elixir
184-
defmodule Notification do
185-
def send(%ChatMessage{user: user, text: text}) do
186-
"#{user}: #{text}"
187-
end
188-
189-
def send(%FriendRequest{sender: sender}) do
190-
"#{sender} wants to be your friend"
191-
end
192-
193-
def send(%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
20080

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`:
20482

20583
```elixir
20684
iex> john = %User{name: "John", age: 27}
20785
%User{age: 27, name: "John"}
208-
iex> broken = Map.put(john, :email, "[email protected]")
209-
%{__struct__: User, age: 27, email: "[email protected]", name: "John"}
210-
iex> is_map(broken)
211-
true
86+
iex> struct!(john, name: "Jane", age: 30)
87+
%User{age: 30, name: "Jane"}
21288
```
21389

214-
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:
21791

21892
```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 :invalid not found in: %User{age: 27, name: "John"}
22397
```
22498

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
226102

227103
Structs are simply maps with a "special" field named `__struct__` that holds the name of the struct:
228104

229105
```elixir
230-
iex> john = %User{name: "John", age: 27}
231-
%User{age: 27, name: "John"}
232106
iex> is_map(john)
233107
true
234108
iex> john.__struct__
@@ -248,3 +122,48 @@ iex> Enum.each(john, fn {field, value} -> IO.puts(value) end)
248122
```
249123

250124
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> defmodule Product do
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> defmodule User do
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> defmodule User do
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> defmodule Car do
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

Comments
 (0)