brettp

brettp

Elixir allows Map functions to manipulate structs

We ran into an issue today with structs that I found a bit surprising.

The essence of the issue is:

  • A struct is a Map under the hood
  • you can manipulate a struct with Map functions, deleting keys that should be there and adding keys that shouldn’t
  • as long as the __struct__ key is still there, the Map is treated as a struct including in Elixir pattern matching

Here’s some code which demonstrates this:

defmodule Sandbox do    
  @enforce_keys [:mandatory]
  defstruct [:mandatory, :optional]

  def struct_foo do
    s1 = %Sandbox{mandatory: "must have this"}
    do_something("s1", s1)

    s2 = Map.delete(s1, :mandatory)
    do_something("s2", s2)

    s3 = Map.put(s1, :bogus, "blah")
    do_something("s3", s3)


  end

  def do_something(s, %Sandbox{} = thing) do
    IO.puts "#{s} #{inspect thing} is a %Sandbox"
  end

  def do_something(s, thing) do
    IO.puts "#{s} #{inspect thing} is just a thing"
  end

end
iex(24)> Sandbox.struct_foo()
s1 %Sandbox{mandatory: "must have this", optional: nil} is a %Sandbox
s2 %{__struct__: Sandbox, optional: nil} is a %Sandbox
s3 %{__struct__: Sandbox, bogus: "blah", mandatory: "must have this", optional: nil} is a %Sandbox

One interesting thing is that IO.inspect seems to know the difference between the original struct and one that’s been hacked into a Map, but Elixir pattern matching doesn’t.

So you almost certainly should not be manipulating a struct in this way, and you certainly shouldn’t be deleting keys from structs. The question is: should (could) Elixir do anything to stop you doing this? Should Map functions check for __struct__ and behave differently if they are asked to do something nefarious to a struct? Should a Map function which manipulates a struct at the very least also remove the __struct__ key so it returns a Map that is not treated as a struct any more?

Most Liked

josevalim

josevalim

Creator of Elixir

Correct.

It could but it would make all maps operations more expensive, which is mostly why we don’t. If we had a static type system, doing Map.put/3 on a struct would certainly fail.

rodrigues

rodrigues

A follow up on the issue described above: turns out it was really a bug in erlang, and Hans Bolinder from Ericsson fixed it today :+1: dialyzer: Handle maps:remove/2 better by uabboli · Pull Request #2392 · erlang/otp · GitHub

tme_317

tme_317

Adding to this point wanted to say I found this library to be awesome for succinctly defining typespecs, enforced keys, and default values on structs so I now use it for every struct… GitHub - ejpcmac/typed_struct: An Elixir library for defining structs with a type without writing boilerplate code. · GitHub

It doesn’t solve all the runtime concerns of the OP but it helps dialyzer help you without too much ceremony!

Where Next?

Popular in Discussions Top

andre1sk
A big advantage to Elixir is all the distributed goodness but for many applications running on multiple nodes having integrated Etcd, Zoo...
New
jeramyRR
This is an interesting article to read. Elixir’s performance, like usual, is excellent. However, it seems like the high CPU usage is co...
New
MarioFlach
Hello, I want to share a project I’ve been working on for a while: https://github.com/almightycouch/gitgud Background Some time ago I ...
New
WildYorkies
It seems that the more I read, the more I find Elixir users speaking about all the ways that Elixir is not good for x, y, and z use cases...
New
lucaong
Hello Elixir and Nerves community, I have been working for a while on an open-source embedded key-value database for Elixir, that I call...
230 13924 124
New
Qqwy
Looking at the stacks that existing large companies have used, WhatsApp internally uses Mnesia to store the messages, while Discord uses ...
New
praveenperera
How We Replaced React with Phoenix By: Thought Bot
New
eteeselink
Hi all, In the last days, two things happened: A blog post titled “They might never tell you it’s broken” made the rounds. It’s about ...
New
Crowdhailer
I’ve been hearing much about the new formatter and it’s something I have been keen to try. I find examples buy far the most illuminating...
248 19204 150
New
AstonJ
I’ve just started the Phoenix part of the utterly brilliant online course by @pragdave. On generating the Phoenix app he uses the --no-ec...
New

Other popular topics Top

vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
gausby
I asked this very same question on twitter and got some interesting feedback, but I thought it would be a good question to ask here as we...
1207 39297 209
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
AstonJ
We’ve put together this wiki for Phoenix LiveView - please feel free to add any info you feel is worth including. What is Phoenix LiveV...
New
Qqwy
Update: How to use the Blogs & Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 126479 1222
New

We're in Beta

About us Mission Statement