7.5 Specifications for Functions

A specification (or contract) for a function is given using the -spec attribute. The general format is as follows:

  1. -spec Module:Function(ArgType1, ..., ArgTypeN) -> ReturnType.

The arity of the function must match the number of arguments, else a compilation error occurs.

This form can also be used in header files (.hrl) to declare type information for exported functions. Then these header files can be included in files that (implicitly or explicitly) import these functions.

Within a given module, the following shorthand suffices in most cases:

  1. -spec Function(ArgType1, ..., ArgTypeN) -> ReturnType.

Also, for documentation purposes, argument names can be given:

  1. -spec Function(ArgName1 :: Type1, ..., ArgNameN :: TypeN) -> RT.

A function specification can be overloaded. That is, it can have several types, separated by a semicolon (;):

  1. -spec foo(T1, T2) -> T3
  2. ; (T4, T5) -> T6.

A current restriction, which currently results in a warning (not an error) by the compiler, is that the domains of the argument types cannot overlap. For example, the following specification results in a warning:

  1. -spec foo(pos_integer()) -> pos_integer()
  2. ; (integer()) -> integer().

Type variables can be used in specifications to specify relations for the input and output arguments of a function. For example, the following specification defines the type of a polymorphic identity function:

  1. -spec id(X) -> X.

Notice that the above specification does not restrict the input and output type in any way. These types can be constrained by guard-like subtype constraints and provide bounded quantification:

  1. -spec id(X) -> X when X :: tuple().

Currently, the :: constraint (read as «is a subtype of») is the only guard constraint that can be used in the when part of a -spec attribute.

Note

The above function specification uses multiple occurrences of the same type variable. That provides more type information than the following function specification, where the type variables are missing:

  1. -spec id(tuple()) -> tuple().

The latter specification says that the function takes some tupleand returns some tuple. The specification with the X typevariable specifies that the function takes a tuple and returnsthe same tuple.

However, it is up to the tools that process the specifications to choose whether to take this extra information into account or not.

The scope of a :: constraint is the (…) -> RetType specification after which it appears. To avoid confusion, it is suggested that different variables are used in different constituents of an overloaded contract, as shown in the following example:

  1. -spec foo({X, integer()}) -> X when X :: atom()
  2. ; ([Y]) -> Y when Y :: number().

Some functions in Erlang are not meant to return; either because they define servers or because they are used to throw exceptions, as in the following function:

  1. my_error(Err) -> erlang:throw({error, Err}).

For such functions, it is recommended to use the special no_return() type for their "return", through a contract of the following form:

  1. -spec my_error(term()) -> no_return().