What does %Mod{} really do?


given the files xx.ex and yy.ex defined as

defmodule XX do
   defstruct [:field]
defmodule YY do
  defdelegate __struct__, to: XX
  defdelegate __struct__(x), to: XX

why does

iex(1)> %XX{} == XX.__struct__()


iex(2)> %YY{} == YY.__struct__()

give different results?

It seems that %Mod{} is not equivalent to Mod.__struct__()

What does %Mod{} really do?


iex(14)> Map.to_list(%YY{})                
[__struct__: YY, field: nil]
iex(15)> Map.to_list(YY.__struct__())
[__struct__: XX, field: nil]


Based on the evidence I suspect that the compiled code (it’s a special form after all) sanitizes the value returned from the function by forcing the __struct__ value to reflect the primary module - but that is just a guess.


My question is this behavior some kind of internal “black magic”, as you suggest, or a bug …

Isn’t %Mod{} supposed to be the same as calling Mod.__struct__()?

I kind of suspect this might be a bug, as per this evidence:

this seems ok

iex(2)> struct!(XX, field: 1)
%XX{field: 1}
iex(3)> %XX{field: 1}
%XX{field: 1}

this seems wrong

iex(4)> struct!(YY, field: 1)
%XX{field: 1}
iex(5)> %YY{field: 1}
%YY{field: 1}

Does anyone have an explanation for this, or is this a bug?

If %Mod{} is supposed to be the same as calling Mod.__struct__() or struct!(Mod), then it is a bug.


I doubt it’s a bug. For example the Access Behavior makes use of that __struct__ value to access the structure specific implementations for navigation and access. If __struct__ was left at XX you would automatically “inherit” all the XX functionality but have no way of overriding it for YY.

But then the behavior of struct!(YY, ...) should also be to return an %YY{...}, but it is returning an %XX{...}

There is some inconsistency somewhere, hence a bug.

iex(16)> struct!(YY,field: 1)
%XX{field: 1}
iex(17)> struct!(%YY{},field: 1)
%YY{field: 1}

I suspect the bug is with Kernel.struct!/2, not Kernel.SpecialForms.%/2.

As you discovered, it only presents when you start defdelegating the constructor and I don’t think this code anticipated that.

So the question is - what are you trying to accomplish by defdelegating the struct constructor to another struct? Sharing code between XX and YY should probably be accomplished via a shared third module.

1 Like

This issue has been solved here.