@@ -843,6 +843,56 @@ defmodule Macro do
843843 `escape/2` is used to escape *values* (either directly passed or variable
844844 bound), while `quote/2` produces syntax trees for
845845 expressions.
846+
847+ ## Customizing struct escapes
848+
849+ By default, structs are escaped to generate the AST for their internal representation.
850+
851+ This approach does not work if the internal representation contains references (like
852+ `Regex` structs), because references can't be escaped.
853+ This is a common issue when working with NIFs.
854+
855+ Let's imagine we have the following struct:
856+
857+ defmodule WrapperStruct do
858+ defstruct [:ref]
859+
860+ def new(...), do: %WrapperStruct{ref: ...}
861+
862+ # efficiently dump to / load from binaries
863+ def dump_to_binary(%WrapperStruct{ref: ref}), do: ...
864+ def load_from_binary(binary), do: %WrapperStruct{ref: ...}
865+ end
866+
867+ Such a struct could not be used in module attributes or escaped with `Macro.escape/2`:
868+
869+ defmodule Foo do
870+ @my_struct WrapperStruct.new(...)
871+ def my_struct, do: @my_struct
872+ end
873+
874+ ** (ArgumentError) cannot inject attribute @my_struct into function/macro because cannot escape #Reference<...>
875+
876+ To address this, structs can re-define how they should be escaped by defining a custom
877+ `__escape__/1` function which returns the AST. In our example:
878+
879+ defmodule WrapperStruct do
880+ # ...
881+
882+ def __escape__(struct) do
883+ # dump to a binary representation at compile-time
884+ binary = dump_to_binary(struct)
885+ quote do
886+ # load from the binary representation at runtime
887+ WrapperStruct.load_from_binary(unquote(Macro.escape(binary)))
888+ end
889+ end
890+ end
891+
892+ Now, our example above will be expanded as:
893+
894+ def my_struct, do: WrapperStruct.load_from_binary(<<...>>)
895+
846896 """
847897 @ spec escape ( term , escape_opts ) :: t ( )
848898 def escape ( expr , opts \\ [ ] ) do
0 commit comments