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
structis aMapunder the hood - you can manipulate a
structwithMapfunctions, deleting keys that should be there and adding keys that shouldn’t - as long as the
__struct__key is still there, theMapis treated as astructincluding 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
If you know you’re working with a struct and want to preserve its integrity use struct/2 or struct!/2.
Most Liked
josevalim
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
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
dialyzer: Handle maps:remove/2 better by uabboli · Pull Request #2392 · erlang/otp · GitHub
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!
Popular in Discussions
Other popular topics
Chat & Discussions>Discussions
Latest on Elixir Forum
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex










