What's 'use' statement in Elixir?

use just calls the __using__ macro on the specified module.

You might have run into that explanation of use but that does not explain much even though that's exactly what it does.

You might have used the use statement in Elixir if you run into using OTP, Phoenix, Ecto or any major library, so did I. But I was confused that what's use, what's the magic happening behind that statement.

In this post we are going to look how use is being used by various Elixir libraries and how it removes the boilerplate that would have been required otherwise.

Lets use use

Elixir has great metaprogramming capabilities and it has macros to generate code at compile time. Wouldn't it be also great if library developers can inject code into your module so that they can decrease the boilerplate for you.

This is where use comes in. When you call use in your module then that modules __using__ macro is called in which the developer can generate whatever code they want. It also takes an arguments list. Let's see an example of it

defmodule AwesomeLibrary do
  defmacro __using__(_) do
    quote do
      def print(s), do: IO.puts(s)
    end
  end
end 

Here we have defined a module in which under __using__ macro we inject a function print/1 which when called prints whatever is passed to it.

defmodule TestLibrary do
  use AwesomeLibrary
end

iex(1)> TestLibrary.print("Hello World")
Hello World
:ok

As you can see that the module TestLibrary has no function print/1 but when we call use AwesomeLibrary it calls the __using__ macro which injects the print/1 function into the TestLibrary module. So in iex calling TestLibrary.print("Hello World") works.

Now let's see some examples of how use is being used in various libraries.

use GenServer

GenServer is a behaviour module which allows you to write process which maintain state and run code synchronously & asynchronously. In Elixir you call use GenServer and implement the relevant behaviour functions you want e.g. init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3. If you have used GenServer you know that you don't have to define every method of the GenServer, only the one you need. Actually if it were Erlang you would have to implement all the functions whether you want it or not, yet you don't have to do that in Elixir, ever wondered why?

Thanks to use you don't have to. Let's see the __using__ of the GenServer

defmacro __using__(_) do
  quote location: :keep do
    @behaviour :gen_server
    
    @doc false
    def init(args) do
      {:ok, args}
    end

    @doc false
    def handle_call(msg, _from, state) do
      # We do this to trick dialyzer to not complain about non-local returns.
      case :random.uniform(1) do
        1 -> exit({:bad_call, msg})
        2 -> {:noreply, state}
      end
    end

    @doc false
    def handle_info(_msg, state) do
      {:noreply, state}
    end

    @doc false
    def handle_cast(msg, state) do
      # We do this to trick dialyzer to not complain about non-local returns.
      case :random.uniform(1) do
        1 -> exit({:bad_cast, msg})
        2 -> {:noreply, state}
      end
    end

    @doc false
    def terminate(_reason, _state) do
      :ok
    end

    @doc false
    def code_change(_old, state, _extra) do
      {:ok, state}
    end

    defoverridable [init: 1, handle_call: 3, handle_info: 2,
                    handle_cast: 2, terminate: 2, code_change: 3]
  end
end

As you can see that the __using__ macro implements the Erlang behaviour :gen_server and it generates the default implementation of all the required functions and also declares them overridable. This way it reduces the amount of code you need to generate as there will already be a default implementation for the function and GenServer will just work.

You can use the same technique when you create new behaviours.

use Ecto

In Ecto if you want to define the schema you call use Ecto.Model in that module. Here the use Ecto.Model acts as an umbrella module that adds all other relevant libraries so you don't have to add bunch of import statements yourself. Let's see the __using__ of Ecto.Model module.

defmacro __using__(_opts) do
  quote do
    use Ecto.Schema
    import Ecto.Changeset
    import Ecto.Query, only: [from: 2]

    import Ecto.Model
    use Ecto.Model.OptimisticLock
    use Ecto.Model.Timestamps
    use Ecto.Model.Dependent
    use Ecto.Model.Autogenerate
    use Ecto.Model.Callbacks
  end
end

Here as you can see it's importing bunch of libraries so that you can use the functions in it without prepending with module name and it also further calls use of other module.

use Protobuf

Protobuf is a language independent protocol developed by Google to serialize structured data to send over the wire and then deserialize it, similar to XML but faster and compact.

In Elixir exprotobuf is library that helps in generating code which makes it pretty straight forward for you to use protobuf.

Following is an example of exprotobuf

defmodule Messages do
  use Protobuf, """
    message Msg {
      required uint32 value = 1;
    }
  """
end

In this example the use takes a string parameter in which we define a Msg type in protobuf DSL which has one field i.e. value. When the code runs you have function Messages.Msg.new, Messages.Msg.encode, Messages.Msg.decode which helps you in creating, encoding and decoding your Msg type respectively.

In __using__ macro protobuf parses over the protobuf DSL and generates respective modules and functions.

Go use use

Now you should have a better idea of how use is being used by various libraries and can leverage it in your modules as well to decrease the boilerplate.