Elixir by Example

Sending and Receiving Messages

Edit any cell and run it in your browser Download source

Processes communicate only by sending messages. send/2 drops a message in the target's mailbox and returns immediately; it never blocks and never fails, even if the target is dead.

send(self(), :first)
send(self(), {:greeting, "hi"})
send(self(), :last)

receive takes the first message matching any clause. Messages from one sender arrive in the order they were sent.

receive do
  msg -> IO.inspect(msg, label: "got")
end
got: :first

Selective receive: a clause can skip ahead to a message that matches, leaving earlier ones in the mailbox for later.

receive do
  :last -> IO.puts("picked :last first")
end

receive do
  {:greeting, text} -> IO.puts("then the greeting: #{text}")
end
picked :last first
then the greeting: hi

after sets a timeout in milliseconds. Without it, receive waits forever.

receive do
  :never_sent -> :ok
after
  50 -> IO.puts("nothing arrived within 50 ms")
end
nothing arrived within 50 ms

after 0 checks the mailbox without waiting. A common idiom to drain all pending messages.

for i <- 1..3, do: send(self(), {:job, i})

drain = fn drain, acc ->
  receive do
    {:job, i} -> drain.(drain, [i | acc])
  after
    0 -> Enum.reverse(acc)
  end
end

IO.inspect(drain.(drain, []), label: "drained")
drained: [1, 2, 3]

Request/reply: include your own pid so the other process knows where to answer, and a unique ref so you can match this reply and not a stale one.

server =
  spawn(fn ->
    receive do
      {:double, n, from, ref} -> send(from, {ref, n * 2})
    end
  end)

ref = make_ref()
send(server, {:double, 21, self(), ref})

receive do
  {^ref, result} -> IO.inspect(result, label: "reply")
after
  1_000 -> IO.puts("timed out")
end
reply: 42

Sending to a dead process is silently ignored.

send(server, :anyone_there?)
IO.puts("send to a dead pid returned without error")
send to a dead pid returned without error

Pitfall: a receive that never matches some message leaves it in the mailbox forever. Every later receive rescans it, so the process gets slower and leaks memory. Long-running loops need a catch-all clause (log and drop), which is exactly what GenServer's handle_info/2 gives you.

Pitfall: don't forget the ^ in {^ref, ...}. Without the pin, ref is rebound and the clause matches any two-element tuple, including replies meant for someone else.