🎓 A1Learner
🔍
0 of 46 lessons completed

Elixir GenServer State

GenServer state is the value a server keeps between messages. The state survives from one call to the next and only changes when a callback returns a new value.

A counter keeps its count between messages

The counter starts at 0. Two counted messages later, a call still sees 2.

defmodule Counter do
  use GenServer
  def init(_arg), do: {:ok, 0}
  def handle_cast(:inc, count), do: {:noreply, count + 1}
  def handle_call(:get, _from, count), do: {:reply, count, count}
end
{:ok, server} = GenServer.start_link(Counter, [])
GenServer.cast(server, :inc)
GenServer.cast(server, :inc)
IO.inspect(GenServer.call(server, :get))
GenServer.stop(server)

An accumulator adds each amount it receives

The total starts at 0. Each added amount grows the same stored total.

defmodule Total do
  use GenServer
  def init(_arg), do: {:ok, 0}
  def handle_cast({:add, amount}, total), do: {:noreply, total + amount}
  def handle_call(:total, _from, total), do: {:reply, total, total}
end
{:ok, server} = GenServer.start_link(Total, [])
GenServer.cast(server, {:add, 5})
GenServer.cast(server, {:add, 7})
IO.inspect(GenServer.call(server, :total))
GenServer.stop(server)

State can hold more than one fact

State can be a small map with two fields. Each visit updates both fields at once.

defmodule Visits do
  use GenServer
  def init(_arg), do: {:ok, %{count: 0, last: "none"}}
  def handle_cast({:visit, name}, state), do: {:noreply, %{state | count: state.count + 1, last: name}}
  def handle_call(:report, _from, state), do: {:reply, Integer.to_string(state.count) <> " visits, last: " <> state.last, state}
end
{:ok, server} = GenServer.start_link(Visits, [])
GenServer.cast(server, {:visit, "Bo"})
GenServer.cast(server, {:visit, "Ana"})
IO.puts(GenServer.call(server, :report))
GenServer.stop(server)

Example

defmodule Score do
  use GenServer
  def init(_arg), do: {:ok, 0}
  def handle_cast(:inc, score), do: {:noreply, score + 1}
  def handle_cast(:dec, score), do: {:noreply, score - 1}
  def handle_call(:get, _from, score), do: {:reply, score, score}
end
{:ok, server} = GenServer.start_link(Score, [])
GenServer.cast(server, :inc)
GenServer.cast(server, :inc)
GenServer.cast(server, :inc)
GenServer.cast(server, :dec)
IO.inspect(GenServer.call(server, :get))
GenServer.stop(server)

Key points

  • State: the value a server carries from one message to the next.
  • init: the callback that chooses the starting state.
  • New state: a callback changes state by returning a new value in its answer.
  • Counter pattern: a number in state that grows by one on each counted message.
  • Accumulator pattern: a total in state that adds each new amount it receives.

In this example, Score starts at 0 through init. Three inc casts raise it to 3, and one dec cast lowers it to 2. A get call then reads 2, showing the state held every change. The server is stopped at the end.

Note

no outside code can reach into state directly, so every change passes through a callback.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

A server starts at 0 and receives three inc casts and one dec cast. What does a get call return?