liveBook · Manning

Cast

As explained in Elixir in Action, Third Edition

A cast is an OTP asynchronous request that delivers a message to a server without returning a response to the caller.

A cast is an OTP asynchronous, fire-and-forget request: a client sends a message and immediately continues without waiting for a response. The server handles the request, updates its state, and loops with the new state. section 6.1.4 section 6.3

Use a cast when:

  • The client does not need confirmation or a returned value. section 6.1.4
  • Immediate responsiveness and scalability matter more than knowing whether processing succeeded. section 7.3.1 section 7.4
  • Avoid it when the caller must know that the request arrived or completed; use a call when stronger consistency is required. section 7.4 section 11.3.4
  • Control producer pressure carefully: casts can let messages accumulate in a server mailbox, consume memory, and eventually threaten the system. section 7.4 section 7.3.3

A cast-backed key-value update

A generic server exposes cast(pid, request), sends {:cast, request}, invokes handle_cast/2, and continues with the returned state. A key-value store can implement put as ServerProcess.cast(pid, {:put, key, value}) while retaining get as a synchronous call.

Listing 7.7 Database process (persistable_todo_cache/lib/todo/database.ex)
defmodule Todo.Database do
  use GenServer
 
  @db_folder "./persist"
 
  def start do
    GenServer.start(__MODULE__, nil,
     name: __MODULE__                                       #1
    )
  end
 
  def store(key, data) do
    GenServer.cast(__MODULE__, {:store, key, data})
  end
 
  def get(key) do
    GenServer.call(__MODULE__, {:get, key})
  end
 
  def init(_) do
    File.mkdir_p!(@db_folder)                               #2
    {:ok, nil}
  end
 
  def handle_cast({:store, key, data}, state) do            #3
    key                                                     #3
    |> file_name()                                          #3
    |> File.write!(:erlang.term_to_binary(data))            #3
                                                            #3
    {:noreply, state}                                       #3
  end                                                       #3
 
  def handle_call({:get, key}, _, state) do                 #4
    data = case File.read(file_name(key)) do                #4
      {:ok, contents} -> :erlang.binary_to_term(contents)   #4
      _ -> nil                                              #4
    end                                                     #4
                                                            #4
    {:reply, data, state}                                   #4
  end
 
  defp file_name(key) do
    Path.join(@db_folder, to_string(key))
  end
end

Inspect a GenServer database where store uses GenServer.cast and get uses GenServer.call, making the responsiveness-versus-certainty trade-off concrete. section 7.3.1

How Elixir in Action, Third Edition covers it

How deep it goes
Explains request tagging, handle_cast/2 callbacks, state transitions, consistency trade-offs, and mailbox overload, but not distributed delivery guarantees.
Examples
Demonstrates casts through a key-value put operation, a persisted database store, and to-do list updates.
What it assumes
Assumes familiarity with Elixir processes, message passing, server state, and OTP server abstractions.
What you can do after
Apply it via GenServer to issue nonblocking updates while choosing calls when confirmation, back pressure, or consistency is necessary.
How it connects
Connects casts with OTP behaviours, GenServer, calls, callbacks, message passing, process mailboxes, scalability, availability, and consistency.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Elixir in Action, Third Edition. The section links point to the parts of the book it draws on.