Introduction
Why do types matter? Types can help catch errors early in development by ensuring that the data your functions and modules work with is of the expected form. This reduces runtime errors and makes your code more predictable and easier to debug. By using typespecs, you also make your code more readable for others (and for your future self), as it clearly communicates what kind of data structures are expected and returned.
In Elixir, we often define structs, and each time we do, we want to define their types as well. The same applies when defining an Ecto.Schema
, we also want to define its types. However, defining types for structs and schemas can be a bit verbose and repetitive. The most popular library used by the community is typed_struct
, but it has lacked maintenance and updates for a while. This is where TypedStructor
comes in to fill the gap, providing a more modern and flexible way to define typed structs and more features.
Typical usage
Here's an example of how to use TypedStructor
to define a typed struct:
defmodule User do
use TypedStructor
typed_structor do
parameter :id
field :id, id
field :name, String.t(), default: "Unknown"
field :age, non_neg_integer(), enforce: true
end
end
In this example:
typed_structor
macro is used to define the struct and its typesparameter
macro is used to define the struct parametersfield
macro is used to define each field of the struct, optionally with a default value and enforcement
This example is equivalent to writing the following code manually:
defmodule User do
defstruct [:id, :name, :age]
@type t(id) :: %__MODULE__{
id: id | nil,
name: String.t(),
age: non_neg_integer()
}
end
Besides defining typed structs, TypedStructor
also provides macros to define typed exceptions and records. Check out the documentation for more information.
defmodule HTTPException do
use TypedStructor
typed_structor definer: :defexception, enforce: true do
field :status, non_neg_integer()
end
@impl Exception
def message(%__MODULE__{status: status}) do
"HTTP status #{status}"
end
end
defmodule TypedStructor.User do
use TypedStructor
typed_structor definer: :defrecord, record_name: :user, record_tag: User, enforce: true do
field :name, String.t()
field :age, pos_integer()
end
end
Extending with plugins
For more customization, TypedStructor
provides a plugin system that allows you to extend the functionality of the library. This is useful when you want to extract some common logic into a separate module.
Many plugins are available, and you can find them in the documentation. To keep the library lightweight, plugins are described in documentation rather than as separate packages, making it easy to copy, paste and customize them for your project. This approach is inspired by the shadcn/ui library.
Some useful plugins include (they are well-tested and documented):
- Add fields docs to the @typedoc
- Implement Access behavior
- Type Only on Ecto Schema
- Derives the Jason.Encoder for struct
Conclusion
As your codebase grows, the importance of using types becomes increasingly evident. Combining types with the documentation significantly enhances both the quality and maintainability of code. TypedStructor
offers a modern, flexible solution to help manage and define typed structs, making Elixir development smoother and more efficient.
Check out the TypedStructor on GitHub and Hex.pm.