View Source TypedStruct (TypedStruct v0.5.2)
TypedStruct is a library for defining structs with a type without writing boilerplate code.
NOTE: This is an active fork of the original typedstruct work by Jean-Philippe Cugnet, which seems to be no longer maintained. This version adds type information to Erlang records and makes the project compile under OTP-26 and later.
Rationale
To define a struct in Elixir, you probably want to define three things:
- the struct itself, with default values,
- the list of enforced keys,
- its associated type.
It ends up in something like this:
defmodule Person do
  @moduledoc """
  A struct representing a person.
  """
  @enforce_keys [:name]
  defstruct name: nil,
            age: nil,
            happy?: true,
            phone: nil
  @typedoc "A person"
  @type t() :: %__MODULE__{
          name: String.t(),
          age: non_neg_integer() | nil,
          happy?: boolean(),
          phone: String.t() | nil
        }
endIn the example above you can notice several points:
- the keys are present in both the defstructand type definition,
- enforced keys must also be written in @enforce_keys,
- if a key has no default value and is not enforced, its type should be nullable.
If you want to add a field in the struct, you must therefore:
- add the key with its default value in the defstructlist,
- add the key with its type in the type definition.
If the field is not optional, you should even add it to @enforce_keys. This is
way too much work for lazy people like me, and moreover it can be error-prone.
It would be way better if we could write something like this:
defmodule Person do
  @moduledoc """
  A struct representing a person.
  """
  use TypedStruct
  typedstruct do
    @typedoc "A person"
    field :name, String.t(), enforce: true
    field :age, non_neg_integer()
    field :happy?, boolean(), default: true
    field :phone, String.t()
  end
endThanks to TypedStruct, this is now possible :)
Usage
Setup
To use TypedStruct in your project, add this to your Mix dependencies:
{:typedstruct, "~> 0.5"}If you do not plan to compile modules using TypedStruct at runtime, you can add
runtime: false to the dependency tuple as TypedStruct is only used at build
time.
If you want to avoid mix format putting parentheses on field definitions,
you can add to your .formatter.exs:
[
  ...,
  import_deps: [:typedstruct]
]General usage
To define a typed struct, use
TypedStruct, then define
your struct within a typedstruct block:
defmodule MyStruct do
  # Use TypedStruct to import the typedstruct macro.
  use TypedStruct
  # Define your struct.
  typedstruct do
    # Define each field with the field macro.
    field :a_string, String.t()
    # You can set a default value.
    field :string_with_default, String.t(), default: "default"
    # You can enforce a field.
    field :enforced_field, integer(), enforce: true
  end
endEach field is defined through the
field/2 macro.
To define a record use the typedrecord block:
defmodule Person do
  use TypedStruct
  typedrecord :person do
    @typedoc "A person"
    field :name, String.t(),
    field :age,  non_neg_integer(), default: 0
  end
endOptions
If you want to enforce all the keys by default, you can do:
defmodule MyStruct do
  use TypedStruct
  # Enforce keys by default.
  typedstruct enforce: true do
    # This key is enforced.
    field :enforced_by_default, term()
    # You can override the default behaviour.
    field :not_enforced, term(), enforce: false
    # A key with a default value is not enforced.
    field :not_enforced_either, integer(), default: 1
  end
endYou can also generate an opaque or private type for the struct by using
the visibility: :opaque | :private | :public option:
defmodule MyOpaqueStruct do
  use TypedStruct
  # Generate an opaque type for the struct.
  typedstruct visibility: :opaque do
    field :name, String.t()
  end
enddefmodule MyPrivateStruct do
  use TypedStruct
  # Generate a private type for the struct.
  typedstruct visibility: :private do
    field :name, String.t()
  end
endIf you often define submodules containing only a struct, you can avoid boilerplate code:
defmodule MyModule do
  use TypedStruct
  # You now have %MyModule.Struct{}.
  typedstruct module: Struct do
    field :field, term()
  end
endDocumentation
To add a @typedoc to the struct type, just add the attribute in the
typedstruct block:
typedstruct do
  @typedoc "A typed struct"
  field :a_string, String.t()
  field :an_int, integer()
endYou can also document submodules this way:
typedstruct module: MyStruct do
  @moduledoc "A submodule with a typed struct."
  @typedoc "A typed struct in a submodule"
  field :a_string, String.t()
  field :an_int, integer()
endPlugins
It is possible to extend the scope of TypedStruct by using its plugin interface,
as described in
TypedStruct.Plugin.
For instance, to automatically generate lenses with the
Lens library, you can use
TypedStructLens and do:
defmodule MyStruct do
  use TypedStruct
  typedstruct do
    plugin TypedStructLens
    field :a_field, String.t()
    field :other_field, atom()
  end
  @spec change(t()) :: t()
  def change(data) do
    # a_field/0 is generated by TypedStructLens.
    lens = a_field()
    put_in(data, [lens], "Changed")
  end
endPresently plugins are not supported by the typedrecord block.
Some available plugins
- typedstruct_lens– Integration with the Lens library.
- typedstruct_legacy_reflection– Re-enables the legacy reflection functions from TypedStruct 0.1.x.
This list is not meant to be exhaustive, please search for “typedstruct” on hex.pm for other results. If you want your plugin to appear here, please open an issue.
What do I get?
When defining an empty typedstruct block:
defmodule Example do
  use TypedStruct
  typedstruct do
  end
endyou get an empty struct with its module type t():
defmodule Example do
  @enforce_keys []
  defstruct []
  @type t() :: %__MODULE__{}
endEach field call adds information to the struct, @enforce_keys and the type
t().
A field with no options adds the name to the defstruct list, with nil as
default. The type itself is made nullable:
defmodule Example do
  use TypedStruct
  typedstruct do
    field :name, String.t()
  end
endbecomes:
defmodule Example do
  @enforce_keys []
  defstruct name: nil
  @type t() :: %__MODULE__{
          name: String.t() | nil
        }
endThe default option adds the default value to the defstruct:
field :name, String.t(), default: "John Smith"
# Becomes
defstruct name: "John Smith"When set to true, the enforce option enforces the key by adding it to the
@enforce_keys attribute.
field :name, String.t(), enforce: true
# Becomes
@enforce_keys [:name]
defstruct name: nilIn both cases, the type has no reason to be nullable anymore by default. In one
case the field is filled with its default value and not nil, and in the other
case it is enforced. Both options would generate the following type:
@type t() :: %__MODULE__{
        name: String.t() # Not nullable
      }Passing opaque: true replaces @type with @opaque in the struct type
specification:
typedstruct opaque: true do
  field :name, String.t()
endgenerates the following type:
@opaque t() :: %__MODULE__{
          name: String.t()
        }When passing module: ModuleName, the whole typedstruct block is wrapped in a
module definition. This way, the following definition:
defmodule MyModule do
  use TypedStruct
  typedstruct module: Struct do
    field :field, term()
  end
endbecomes:
defmodule MyModule do
  defmodule Struct do
    @enforce_keys []
    defstruct field: nil
    @type t() :: %__MODULE__{
            field: term() | nil
          }
  end
endTo define a typed record, the following definition of the typedrecord:
defmodule Person do
  use TypedStruct
  typedrecord :person do
    @typedoc "A person"
    field :name, String.t()
    field :age,  non_neg_integer(), default: 0
  end
endbecomes:
defmodule Person do
  use Record
  Record.defrecord(:person, name: nil, age: 0)
  @type person :: {:person, String.t()|nil, non_neg_integer()}
endSummary
Functions
Defines a field in a typed struct.
Registers a plugin for the currently defined struct.
Defines a typed record.
Defines a typed struct.
Functions
Defines a field in a typed struct.
Example
# A field named :example of type String.t()
field :example, String.t()Options
- default- sets the default value for the field
- enforce- if set to true, enforces the field and makes its type non-nullable
Registers a plugin for the currently defined struct.
Example
typedstruct do
  plugin MyPlugin
  field :a_field, String.t()
endFor more information on how to define your own plugins, please see
TypedStruct.Plugin. To use a third-party plugin, please refer directly to
its documentation.
Defines a typed record.
Inside a typedrecord block, each field is defined through the field/2
macro.
Options
- tag- if set, used as the- tagparameter passed to- Record.defrecord/3.
- visibility- one of the values:- :public(default),- :private,- :opaque.
- module- if set, creates the struct in a submodule named- module.
Defines a typed struct.
Inside a typedstruct block, each field is defined through the field/2
macro.
Options
- enforce- if set to true, sets- enforce: trueto all fields by default. This can be overridden by setting- enforce: falseor a default value on individual fields.
- visibility- one of the values:- :public(default),- :private,- :opaque.
- module- if set, creates the struct in a submodule named- module.
- opaque- (deprecated) if set to true, creates an opaque type for the struct.
Examples
defmodule MyStruct do
  use TypedStruct
  typedstruct do
    field :field_one, String.t()
    field :field_two, integer(), enforce: true
    field :field_three, boolean(), enforce: true
    field :field_four, atom(), default: :hey
  end
endThe following is an equivalent using the enforce by default behaviour:
defmodule MyStruct do
  use TypedStruct
  typedstruct enforce: true do
    field :field_one, String.t(), enforce: false
    field :field_two, integer()
    field :field_three, boolean()
    field :field_four, atom(), default: :hey
  end
endYou can create the struct in a submodule instead:
defmodule MyModule do
  use TypedStruct
  typedstruct module: Struct do
    field :field_one, String.t()
    field :field_two, integer(), enforce: true
    field :field_three, boolean(), enforce: true
    field :field_four, atom(), default: :hey
  end
end