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?

First Post!

LostKobrakai

LostKobrakai

If you know you’re working with a struct and want to preserve its integrity use struct/2 or struct!/2.

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

AstonJ
Please see the new poll here: Which code editor or IDE do you use? (Poll) (2022 Edition) It’s been a while since we first asked this, I...
208 31707 143
New
jesse
Hi everyone, I hesitated to post this here because I don’t want you to think I’m spamming, but I’ve been working on a Platform-as-a-Serv...
New
joeerl
I’m playing with Elixir - It’s fun. I think @rvirding does give Elixir courses these days. Re: files and database - when I given Erlang ...
New
jer
I’ve been using umbrellas for a while, and generally started off (on greenfield projects at least) by isolating subapps based on clearly ...
New
chulkilee
Here are the list of HTTP client libraries/wrappers, and some thoughts on HTTP client in general. I’d like to hear from others how they w...
New
pillaiindu
I want to convert a Phoenix LiveView CRUD website to a CRUD mobile app. What do you think is the easiest way to do so?
New
crispinb
On reading dhh’s latest The One Person Framework it strikes me that Phoenix with LiveView is already pretty much this. However, never hav...
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
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
dogweather
I wrote this comment on r/haskell, and it’s not popular there. :wink: But I think I’m on to something… Haskell reminds me of Java, and e...
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 40082 209
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New

We're in Beta

About us Mission Statement