Using `@impl Phoenix.LiveView` instead of `@impl true` for clear callback contracts

almirsarajcic

almirsarajcic

8 days ago

Stop using @impl true in your Phoenix modules. The Elixir compiler provides much better safety and clarity when you specify the exact behaviour module name.

defmodule MyLive do
  use Phoenix.LiveView

  # ❌ WRONG - Lazy and unclear
  @impl true
  def mount(_params, _session, socket) do
    {:ok, socket}
  end

  # ✅ CORRECT - Crystal clear which behaviour
  @impl Phoenix.LiveView
  def mount(_params, _session, socket) do
    {:ok, socket}
  end
end

Why explicit module names matter

Multiple behaviours create ambiguity. When your module implements several behaviours, @impl true becomes confusing:

defmodule MyServer do
  @behaviour GenServer
  @behaviour Phoenix.Channel

  @impl true  # Which behaviour does this implement??
  def init(args), do: {:ok, args}

  # Both GenServer AND Phoenix.Channel have init/1 callbacks!
  # Compiler warning: conflicting behaviours found
end

Compile-time safety kicks in. The compiler actively validates your @impl declarations:

defmodule WrongExample do
  use Phoenix.LiveView

  @impl Phoenix.LiveComponent  # Wrong behaviour!
  def mount(_params, _session, socket), do: {:ok, socket}

  # WARNING: got "@impl Phoenix.LiveComponent" for function mount/3
  # but this behaviour was not declared with @behaviour
end

Consistency enforcement. If you mark one callback with @impl, you must mark ALL callbacks:

defmodule InconsistentExample do
  use Phoenix.LiveView

  @impl Phoenix.LiveView  # Marked this one...
  def mount(_params, _session, socket), do: {:ok, socket}

  # But forgot this one - COMPILER WARNING!
  def handle_event("save", _params, socket), do: {:noreply, socket}
  # WARNING: module attribute @impl was not set for function handle_event/3
end

Common Phoenix patterns

# LiveView modules
@impl Phoenix.LiveView
def mount(_params, _session, socket), do: {:ok, socket}

@impl Phoenix.LiveView
def handle_event("save", _params, socket), do: {:noreply, socket}

@impl Phoenix.LiveView
def render(assigns), do: ~H"<div>LiveView</div>"

# LiveComponent modules
@impl Phoenix.LiveComponent
def update(assigns, socket), do: {:ok, assign(socket, assigns)}

@impl Phoenix.LiveComponent
def render(assigns), do: ~H"<div>Component</div>"

# GenServer modules
@impl GenServer
def init(state), do: {:ok, state}

@impl GenServer
def handle_call(msg, _from, state), do: {:reply, msg, state}

Using explicit behaviour names makes your code self-documenting. Future developers (including yourself) immediately understand which contract each function fulfills without scrolling to find behaviour declarations.

The compiler becomes your friend, catching mismatched signatures and missing implementations before they reach production.