The right way to update Elixir structs (and how not to do it 😁)


I think there is a right way (and a wrong way) to update Elixir structs – at least that’s if you want to know when you accidentally type the wrong thing!

Since structs are maps, we might be tempted to use Map.put/3.

iex> user = %User{name: "Boromir", admin: true}
iex> user |> Map.put(:admin, false)
# => %User{name: "Boromir", admin: false}

See, it works!…

… until you make a typo.

iex> user = %User{name: "Boromir", admin: true}
iex> user |> Map.put(:amdin, false) # <- typo!!
# => %{name: "Boromir", __struct__: User, admin: true, amdin: false}

😱 that’s not what we meant to do, is it?

Compare that to the special syntax to update structs:

iex> user = %User{name: "Boromir", admin: true}
iex> %{user | admin: false }
%User{name: "Boromir", admin: false}

iex> %{user | amdin: false} # <- typo!!
** (KeyError) key :amdin not found in: %User{name: "Boromir", admin: true}. Did you mean:

      * :admin

🤩 look at that beautiful message! Elixir’s got our back!

But I already know what you’ll be saying…

It's not pipe friendly!

I hear you. I hear you.

Thankfully, the Elixir team heard that too, and they introduced Map.replace/3 and Map.replace!/3.

Check it out!

iex> user |> Map.replace(:amdin, false)
%User{name: "Boromir", admin: true}

As you can see, Map.replace/3 doesn’t put a non-existent value into the map. So the typo doesn’t just magically add keys to your map.

But… we now have a silent error. Maybe we want to be loud!

iex> user |> Map.replace!(:amdin, false)
** (KeyError) key :amdin not found in: %User{name: "Boromir", admin: true}. Did you mean:

      * :admin

There you go! Two great ways to update your structs – safety included.

Want the latest Elixir Streams in your inbox?

    No spam. Unsubscribe any time.