@@ -843,6 +843,66 @@ 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+ ## Dealing with references and other runtime values
848+
849+ Macros work at compile-time and therefore `Macro.escape/1` can only escape values
850+ that are valid during compilation, such as numbers, atoms, tuples, maps, binaries,
851+ etc.
852+
853+ However, you may have values at compile-time which cannot be escaped, such as
854+ `reference`s and `pid`s, since the process or memory address they point to will
855+ no longer exist once compilation completes. Attempting to escape said values will
856+ raise an exception. This is a common issue when working with NIFs.
857+
858+ Luckily, Elixir v1.19 introduces a mechanism that allows those values to be escaped,
859+ as long as they are encapsulated by a struct within a module that defines the
860+ `__escape__/1` function. This is possible as long as the reference has a natural
861+ text or binary representation that can be serialized during compilation.
862+
863+ Let's imagine we have the following struct:
864+
865+ defmodule WrapperStruct do
866+ defstruct [:ref]
867+
868+ def new(...), do: %WrapperStruct{ref: ...}
869+
870+ # efficiently dump to / load from binaries
871+ def dump_to_binary(%WrapperStruct{ref: ref}), do: ...
872+ def load_from_binary(binary), do: %WrapperStruct{ref: ...}
873+ end
874+
875+ Such a struct could not be used in module attributes or escaped with `Macro.escape/2`:
876+
877+ defmodule Foo do
878+ @my_struct WrapperStruct.new(...)
879+ def my_struct, do: @my_struct
880+ end
881+
882+ ** (ArgumentError) cannot inject attribute @my_struct into function/macro because cannot escape #Reference<...>
883+
884+ To address this, structs can re-define how they should be escaped by defining a custom
885+ `__escape__/1` function which returns the AST. In our example:
886+
887+ defmodule WrapperStruct do
888+ # ...
889+
890+ def __escape__(struct) do
891+ # dump to a binary representation at compile-time
892+ binary = dump_to_binary(struct)
893+ quote do
894+ # load from the binary representation at runtime
895+ WrapperStruct.load_from_binary(unquote(Macro.escape(binary)))
896+ end
897+ end
898+ end
899+
900+ Now, our example above will be expanded as:
901+
902+ def my_struct, do: WrapperStruct.load_from_binary(<<...>>)
903+
904+ When implementing `__escape__/1`, you must ensure that the quoted expression
905+ will evaluate to a struct that represents the one given as argument.
846906 """
847907 @ spec escape ( term , escape_opts ) :: t ( )
848908 def escape ( expr , opts \\ [ ] ) do
0 commit comments