Args in gen_*:init/1

This is exactly why I have the exact opposite rule as @vances’ one:

Never use lists in the Args/InitArgs parameter for gen_*:start[_link](…) / init/1.

The erlang docs provide an example that includes…

start_link() ->
    gen_server:start_link({local, ch3}, ch3, [], []).

…and…

init(_Args) ->
    {ok, channels()}.

And I think that example, and other similar pieces of documentation, are misleading at best. Mainly for two reasons:

  1. Using an empty list if you don’t need to pass anything to init/1 may lead people to believe that you’re passing 0 arguments to the init function (because sometimes arguments are passed down to some functions like supervisor:start_child/2 as lists. But that’s not true. With an empty list, you’re passing a single argument to init/1, as usual. It’s just that in this case it’s a list. If you don’t need to pass any external values down to init/1, I recommend the usage of an empty map (#{}), an empty tuple ({}), the atom undefined, or something even more explicit, like the atom no_arguments.
  2. Calling the single parameter of init/1 Args. It’s not wrong, but again it may lead people to believe that since it’s written in plural, it has to be a list of arguments. When in reality it’s a single argument.

Using this example code to build servers has led more people than I count to write stuff like this…

-module my_server.
% …exports and everything else…
start_link(AParam) ->
  gen_server:start_link(?MODULE, [AParam], []).

init(AParam) ->
  …

Which is a code that compiles and it then fails mysteriously when it’s executed. It has produced many headaches over the years since that example was written.

3 Likes