Subscribe to access all episodes. View plans →

#116: @impl Attribute

Published June 29, 2020

Elixir 1.10


We have an Elixir application that has a single module named Teacher, which implements the GenServer behavoir.

If you’re not familiar with GenServers check out episode #12 where go over how they work.

What this GenServer does is simply adds and subtracts from a running total that starts at 0. Let’s quickly test it out to see that it works.

We’ll start up the GenServer with Teacher.start_link. Then with the pid we can get the total, add to that total, and then subtract from it.

$ iex -S mix
Interactive Elixir (1.10.3)
> {:ok, pid} = Teacher.start_link()
{:ok, #PID<0.151.0>}
> Teacher.total(pid)
0
> Teacher.add(pid, 5)
5
> Teacher.subtract(pid, 2)
3

Now if we we open the Teacher module it’s is a pretty straightforward module, but it can still take a minute to discern what functions implement the GenServer callbacks and which ones don’t.

Elixir 1.5 introduced a solution this problem. You can mark what functions in a module are implementations of a callback using the @impl attribute.

This makes it easier to see what functions implement callbacks when you first look at a module. Let’s go ahead and update our module to use the @impl attribute. To start we’ll add @impl true above our init callback.

lib/teacher.ex

...
@impl true
def init(state) do
  {:ok, state}
end
...

Now if we start our project we get a warning. When we add @impl true to one callback, Elixir will tell us to implement them for the other callbacks in the module for consistency.

$ iex -S mix
warning: module attribute @impl was not set for function handle_call/3 callback (specified in GenServer). This either means you forgot to add the "@impl true" annotation before the definition or that you are accidentally overriding this callback
  lib/teacher.ex:26: Teacher (module)
>

So let’s go back to our module add the @impl attribute to all of our callbacks.

lib/teacher.ex

...

@impl true
def init(state) do
  {:ok, state}
end

@impl true
def start_link do
  GenServer.start_link(__MODULE__, 0)
end

@impl true
def handle_call({:add, num}, _from, sum) do
  sum = num + sum
  {:reply, sum, sum}
end

@impl true
def handle_call({:subtract, num}, _from, sum) do
  sum = sum - num
  {:reply, sum, sum}
end

@impl true
def handle_call(:total, _from, sum) do
  {:reply, sum, sum}
end

...

Then when we go to start our project again Elixir tells us there’s an error - start_link is not actually a callback and then it lists the available callbacks. This is a really nice feature - Elixir will error telling us we’ve tried to include the @impl attribute for a function that doesn’t implement a callback.

$ iex -S mix
Compiling 1 file (.ex)
warning: got "@impl true" for function start_link/0 but no behaviour specifies such callback. The known callbacks are:

  * GenServer.code_change/3 (function)
  * GenServer.format_status/2 (function)
  * GenServer.handle_call/3 (function)
  * GenServer.handle_cast/2 (function)
  * GenServer.handle_continue/2 (function)
  * GenServer.handle_info/2 (function)
  * GenServer.init/1 (function)
  * GenServer.terminate/2 (function)

  lib/teacher.ex:22: Teacher (module)

Interactive Elixir (1.10.3) - press Ctrl+C to exit (type h() ENTER for help)

Let’s remove it from the start_link function. Then if we start our project again everything works.

$ iex -S mix
Interactive Elixir (1.10.3)

And here we actually don’t need to add the @impl attribute to each handle_call in the group. We only need to include it to the first one.

Now when we need to go back update this module or when another developer needs to make changes, it will be clear which functions implement callbacks, making our code much more readable.

© 2020 HEXMONSTER LLC