Types and Specs
Elixir basic types, type specs, and dialyzer for static type analysis
Basic types
| Type | Example | Notes |
|---|---|---|
| Integer | 1, 0xFF, 0b1010, 0o777 |
Arbitrary precision |
| Float | 3.14, 1.0e10 |
IEEE 754 double |
| Boolean | true, false |
Atoms underneath |
| Atom | :ok, :error, :hello |
Interned strings |
| String | "hello" |
UTF-8 binary |
| Charlist | 'hello' |
List of codepoints |
| List | [1, :a, "hi"] |
Linked list |
| Tuple | {1, :ok} |
Fixed-size, indexed |
| Map | %{a: 1} |
Key-value |
| Function | fn x -> x end |
Anonymous / named |
| PID | self() |
Process identifier |
| Reference | make_ref() |
Unique reference |
| Nil | nil |
Atom, represents null |
Type checks
is_atom(:ok) # => true
is_boolean(true) # => true
is_integer(1) # => true
is_float(3.14) # => true
is_number(1) # => true (int or float)
is_binary("hi") # => true
is_list([1, 2]) # => true
is_tuple({1, 2}) # => true
is_map(%{a: 1}) # => true
is_function(fn -> end) # => true
is_pid(self()) # => true
is_nil(nil) # => trueType specifications
Elixir supports optional type annotations via @type, @typep, @opaque, @spec, and @callback.
Defining types
defmodule User do
@type t :: %__MODULE__{
name: String.t(),
age: non_neg_integer(),
email: String.t() | nil
}
@type id :: pos_integer()
defstruct [:name, :age, :email]
endCommon built-in types
| Type | Meaning |
|---|---|
integer() |
Any integer |
float() |
Any float |
number() |
Integer or float |
boolean() |
true or false |
atom() |
Any atom |
String.t() |
UTF-8 string (binary) |
binary() |
Any binary |
list(any()) |
A list |
[type()] |
A list of type |
map() |
Any map |
%{key: type} |
Map with specific keys |
tuple() |
Any tuple |
{type1, type2} |
2-tuple with specific types |
pid() |
Process identifier |
reference() |
Unique reference |
any() |
Any value |
none() |
No value (unreachable) |
iodata() |
binary() | list() |
charlist() |
list(integer()) |
non_neg_integer() |
Integer ≥ 0 |
pos_integer() |
Integer > 0 |
neg_integer() |
Integer < 0 |
Union and nullable types
@type status :: :ok | :error | :pending
@type maybe_string :: String.t() | nil
@type result :: {:ok, term()} | {:error, String.t()}Remote types
@type uri :: URI.t()
@type date :: Date.t()Function specs
defmodule Math do
@spec add(number(), number()) :: number()
def add(a, b), do: a + b
@spec divide(number(), number()) :: {:ok, float()} | {:error, String.t()}
def divide(_, 0), do: {:error, "division by zero"}
def divide(a, b), do: {:ok, a / b}
@spec find_user(pos_integer()) :: User.t() | nil
def find_user(id), do: Repo.get(User, id)
endCallback specs (behaviours)
defmodule Parser do
@callback parse(String.t()) :: {:ok, term()} | {:error, String.t()}
@callback extensions() :: [String.t()]
endType visibility
| Declaration | Visibility |
|---|---|
@type |
Public — can be used by other modules |
@typep |
Private — only within the module |
@opaque |
Public name, private structure — callers can’t see internals |
defmodule SecretBox do
@opaque t :: %__MODULE__{value: term()}
defstruct [:value]
endDialyzer
Dialyzer is a static analysis tool that identifies type discrepancies.
# Install dialyxir
mix deps.add dialyxir
# Build the PLT (persistent lookup table) — first time only
mix dialyzer --plt
# Run analysis
mix dialyzer
# Run with helpful explanations
mix dialyzer --format explainGradual type checking with gradualizer
For stricter type checking, consider Gradualizer or the upcoming Elixir set-theoretic types (Elixir 1.17+).
Elixir 1.17+ pattern-based types
Elixir 1.17 introduced set-theoretic type checking:
# Elixir 1.17+ can infer and warn about type mismatches
def example do
x = if true, do: 1, else: :ok # x is integer() | atom()
String.length(x) # warning: x may not be a string
end