State Record And Type

View Source

Modules implementing the OTP behaviors listed below should define a #state{} record and a corresponding state type (public - type() - or private - opaque).

  • gen_server
  • gen_event
  • gen_fsm
  • gen_statem
  • supervisor_bridge

Note: if used together with export_used_types, the state record should be defined as a private type (opaque()), and should be exported.

opaque() was added in 4.0.0.

Avoid

No type declared, no type exported:

-behaviour(gen_server).

-export([init/1]).

-record(state, {cur_value :: undefined | term()}).

init({}) ->
    {ok, #state{cur_value = undefined}}.

Prefer

Type declared, type exported (as private):

-behaviour(gen_server).

-export([init/1]).

-record(state, {cur_value :: undefined | term()}).
-opaque state() :: #state{}.
-export_type([state/0]).

-spec init({}) -> {ok, state()}.
init({}) ->
    {ok, #state{cur_value = undefined}}.

Rationale

Modules that implement core OTP behaviors such as gen_server, gen_event, ... manage internal state that is passed between callbacks. To improve readability, consistency, and type safety (especially when using tools like Dialyzer), these modules should explicitly define a #state{} record to represent their internal state, and a corresponding -type state() or -opaque state() to abstract or encapsulate its structure.

This way, the state structure is clearly defined and documented, and type specifications referring to the state are consistent across callbacks.

Options

  • None.

Example configuration

{elvis_style, state_record_and_type, #{}}