Elixir结构体 #

一、结构体基础 #

1.1 定义结构体 #

结构体是带有固定字段的映射:

elixir
defmodule User do
  defstruct [:name, :email, :age]
end

1.2 创建结构体 #

elixir
iex(1)> %User{name: "Alice", email: "alice@example.com", age: 30}
%User{age: 30, email: "alice@example.com", name: "Alice"}

iex(2)> %User{}
%User{age: nil, email: nil, name: nil}

1.3 默认值 #

elixir
defmodule User do
  defstruct name: "Anonymous", email: nil, age: 0
end

%User{}
%User{name: "Alice"}

1.4 访问字段 #

elixir
iex(1)> user = %User{name: "Alice", email: "alice@example.com", age: 30}
%User{age: 30, email: "alice@example.com", name: "Alice"}

iex(2)> user.name
"Alice"

iex(3)> user.email
"alice@example.com"

1.5 更新字段 #

elixir
iex(1)> user = %User{name: "Alice"}
%User{age: 0, email: nil, name: "Alice"}

iex(2)> %{user | name: "Bob"}
%User{age: 0, email: nil, name: "Bob"}

iex(3)> %{user | name: "Bob", age: 25}
%User{age: 25, email: nil, name: "Bob"}

二、结构体特性 #

2.1 结构体是映射 #

elixir
iex(1)> user = %User{name: "Alice"}
%User{age: 0, email: nil, name: "Alice"}

iex(2)> is_map(user)
true

iex(3)> map_size(user)
4

2.2 __struct__字段 #

结构体包含 __struct__ 字段:

elixir
iex(1)> user = %User{name: "Alice"}
%User{age: 0, email: nil, name: "Alice"}

iex(2)> user.__struct__
User

2.3 类型检查 #

elixir
iex(1)> user = %User{name: "Alice"}
%User{age: 0, email: nil, name: "Alice"}

iex(2)> is_struct(user)
true

iex(3)> is_struct(user, User)
true

iex(4)> is_struct(%{}, User)
false

三、模式匹配 #

3.1 基本匹配 #

elixir
iex(1)> %User{name: name} = %User{name: "Alice", email: "alice@example.com"}
%User{age: 0, email: "alice@example.com", name: "Alice"}

iex(2)> name
"Alice"

3.2 函数参数匹配 #

elixir
defmodule User do
  defstruct [:name, :email, :age]

  def greet(%User{name: name}), do: "Hello, #{name}!"

  def adult?(%User{age: age}) when age >= 18, do: true
  def adult?(%User{}), do: false
end

3.3 匹配特定结构体 #

elixir
defmodule Handler do
  def handle(%User{name: name}), do: "User: #{name}"
  def handle(%Post{title: title}), do: "Post: #{title}"
  def handle(_), do: "Unknown"
end

四、类型规范 #

4.1 定义类型 #

elixir
defmodule User do
  @type t :: %__MODULE__{
    name: String.t(),
    email: String.t(),
    age: non_neg_integer()
  }

  defstruct [:name, :email, :age]
end

4.2 使用类型 #

elixir
defmodule UserService do
  @spec create(String.t(), String.t(), non_neg_integer()) :: User.t()
  def create(name, email, age) do
    %User{name: name, email: email, age: age}
  end

  @spec update(User.t(), keyword()) :: User.t()
  def update(%User{} = user, attrs) do
    struct(user, attrs)
  end
end

五、实现协议 #

5.1 String.Chars协议 #

elixir
defmodule User do
  defstruct [:name, :email]

  defimpl String.Chars do
    def to_string(%User{name: name}), do: "User(#{name})"
  end
end

to_string(%User{name: "Alice"})

5.2 Inspect协议 #

elixir
defmodule User do
  defstruct [:name, :email, :password]

  defimpl Inspect do
    def inspect(%User{name: name, email: email}, _opts) do
      "#User<name: #{name}, email: #{email}>"
    end
  end
end

inspect(%User{name: "Alice", email: "alice@example.com", password: "secret"})

5.3 Enumerable协议 #

elixir
defmodule Pair do
  defstruct [:first, :second]

  defimpl Enumerable do
    def count(_pair), do: {:ok, 2}

    def member?(%Pair{first: first}, element) when first == element, do: {:ok, true}
    def member?(%Pair{second: second}, element) when second == element, do: {:ok, true}
    def member?(_, _), do: {:ok, false}

    def reduce(%Pair{first: first, second: second}, acc, fun) do
      case acc do
        {:cont, acc} -> fun.(second, fun.(first, acc))
        {:halt, acc} -> {:halted, acc}
        {:suspend, acc} -> {:suspended, acc, &reduce(%Pair{first: first, second: second}, &1, fun)}
      end
    end

    def slice(_pair), do: {:error, __MODULE__}
  end
end

Enum.to_list(%Pair{first: 1, second: 2})

5.4 Access协议 #

elixir
defmodule User do
  defstruct [:name, :email, :settings]

  defimpl Access do
    def fetch(%User{settings: settings}, key) do
      Map.fetch(settings || %{}, key)
    end

    def get_and_update(%User{settings: settings} = user, key, fun) do
      {current, new_settings} = Map.get_and_update(settings || %{}, key, fun)
      {current, %{user | settings: new_settings}}
    end

    def pop(%User{settings: settings} = user, key) do
      {current, new_settings} = Map.pop(settings || %{}, key)
      {current, %{user | settings: new_settings}}
    end
  end
end

user = %User{settings: %{theme: "dark"}}
user[:theme]

六、派生协议 #

6.1 使用@derive #

elixir
defmodule User do
  @derive {Jason.Encoder, only: [:name, :email]}
  defstruct [:name, :email, :password]
end

Jason.encode(%User{name: "Alice", email: "alice@example.com", password: "secret"})

6.2 派生多个协议 #

elixir
defmodule User do
  @derive [Jason.Encoder, Access]
  defstruct [:name, :email]
end

七、结构体最佳实践 #

7.1 完整示例 #

elixir
defmodule MyApp.User do
  @moduledoc """
  User struct and related functions.
  """

  @type t :: %__MODULE__{
    id: integer() | nil,
    name: String.t(),
    email: String.t(),
    age: non_neg_integer(),
    inserted_at: DateTime.t() | nil,
    updated_at: DateTime.t() | nil
  }

  @derive {Jason.Encoder, except: [:__struct__]}
  defstruct [
    :id,
    :name,
    :email,
    :age,
    :inserted_at,
    :updated_at
  ]

  @doc "Creates a new user"
  @spec new(String.t(), String.t(), non_neg_integer()) :: t()
  def new(name, email, age) do
    %__MODULE__{
      name: name,
      email: email,
      age: age
    }
  end

  @doc "Validates user"
  @spec valid?(t()) :: boolean()
  def valid?(%__MODULE__{name: name, email: email, age: age}) do
    is_binary(name) and is_binary(email) and is_integer(age) and age >= 0
  end

  @doc "Updates user"
  @spec update(t(), keyword()) :: t()
  def update(%__MODULE__{} = user, attrs) do
    struct(user, Keyword.take(attrs, [:name, :email, :age]))
  end
end

7.2 工厂函数 #

elixir
defmodule User do
  defstruct [:id, :name, :email]

  def new(attrs \\ []) do
    struct!(__MODULE__, Keyword.put_new(attrs, :id, generate_id()))
  end

  defp generate_id, do: :rand.uniform(1000)
end

八、结构体与映射的区别 #

特性 结构体 映射
固定字段
类型信息
模式匹配 精确 部分
默认值 支持 不支持
协议实现 可自定义 有限

九、总结 #

本章学习了:

特性 示例
定义 defstruct [:name, :email]
创建 %User{name: "Alice"}
访问 user.name
更新 %{user | name: "Bob"}
类型 @type t :: %__MODULE__{}
协议 defimpl String.Chars

准备好学习协议了吗?让我们进入下一章。

最后更新:2026-03-27