Skip to main content

README.md

# TypeStruct

TypeStruct defines structs and their types together.

## Installation

Add `:type_struct` to your dependencies:

```elixir
def deps do
  [
    {:type_struct, "~> 1.0"}
  ]
end
```

## Usage

```elixir
defmodule Point do
  use TypeStruct

  defstruct x: integer,
            y: integer
end
```

TypeStruct can also define nested struct modules:

```elixir
defmodule Accounts do
  use TypeStruct

  defstruct User,
            id: integer,
            name: String.t() \\ "",
            eye_color: :black | :blue | :brown | :green

  defstruct Group,
            id: integer,
            name: String.t(),
            users: [User.t()] \\ []
end
```

Each field is written as `name: type`. A default can be given with `\\`:

```elixir
defstruct User, name: String.t() \\ ""
```

Fields without defaults are required via `@enforce_keys`:

```elixir
defstruct User, id: integer, name: String.t() \\ ""
```

If you want a field to default to `nil`, declare that default explicitly:

```elixir
defstruct User, email: String.t() | nil \\ nil
```

## Types

By default, TypeStruct defines a public `t/0` type for every struct:

```elixir
defmodule Accounts do
  use TypeStruct

  defstruct User, id: integer
end
```

The `Accounts.User` example above is equivalent to:

```elixir
defmodule Accounts.User do
  defstruct [:id]

  @type t() :: %__MODULE__{id: integer}
end
```

You can choose a different type attribute or name:

```elixir
defstruct User, type(fields), id: integer
# @type fields() :: %__MODULE__{id: integer}

defstruct User, typep(fields), id: integer
# @typep fields() :: %__MODULE__{id: integer}

defstruct User, opaque(t), id: integer
# @opaque t() :: %__MODULE__{id: integer}
```

The field types are regular Elixir typespecs. Local types referenced by nested
structs are resolved against the parent module, while built-in types are left
untouched:

```elixir
defmodule Catalog do
  use TypeStruct

  @type color :: :red | :green | :blue

  defstruct Product,
            color: color,
            tags: nonempty_list(String.t())
end
```

Here `color` means `Catalog.color/0`, while `nonempty_list/1` is the built-in
type.

## Typedocs

A `@typedoc` placed immediately before a nested `defstruct` is moved into the
generated module:

```elixir
defmodule Accounts do
  use TypeStruct

  @typedoc "A user in the current account."
  defstruct User, id: integer
end
```