15 Structs

在早期的几章中,我们已经学习了表单:

  1. iex> map = %{a: 1, b: 2}
  2. %{a: 1, b: 2}
  3. iex> map[:a]
  4. 1
  5. iex> %{map | a: 3}
  6. %{a: 3, b: 2}

Structs是在表单上的扩展,带来了默认值,编译时保证,和Elixir中的多态。

定义一个struct,我们只需要去调用在一个模块内调用defstruct/1

  1. iex> defmodule User do
  2. ...> defstruct name: "jose", age: 27
  3. ...> end
  4. {:module, User,
  5. <<70, 79, 82, ...>>, {:__struct__, 0}}

现在我们用语法%User{}来能创建这个stuct的一个“实例”:

  1. iex> %User{}
  2. %User{name: "jose", age: 27}
  3. iex> %User{name: "eric"}
  4. %User{name: "eric", age: 27}
  5. iex> is_map(%User{})
  6. true

struct提供了编译时的某一个数值存在于struct中的保证:

  1. iex> %User{oops: :field}
  2. ** (CompileError) iex:3: unknown key :oops for struct User

当我们谈论表单是,我们演示了我们才能访问和更新表单中的一个域。这同样适用于structs:

  1. iex> jose = %User{}
  2. %User{name: "jose", age: 27}
  3. iex> jose.name
  4. "jose"
  5. iex> eric = %{jose | name: "eric"}
  6. %User{name: "eric", age: 27}
  7. iex> %{user | oops: :field}
  8. ** (ArgumentError) argument error

有了更新的语法,虚拟机就能直到没有新的键会被加入表单和struct,运行表单在内存中共享它们的结构。在上面的例子中,joseeric共享了通过一个内存中的键结构。

Struct也能被用于模式匹配,它们保证了两边的struct都是统一类型的:

  1. iex> %User{name: name} = jose
  2. %User{name: "jose", age: 27}
  3. iex> name
  4. "jose"
  5. iex> %User{} = %{}
  6. ** (MatchError) no match of right hand side value: %{}

匹配之所以能完成,是因为struct在表单内部存储了一个叫__struct__的域:

  1. iex> jose.__struct__
  2. User

总的来说,一个struct只不过是一个带有默认域的普通表单。注意我们说它是普通的表单因为实现的表单的所有协议,没有一个能被用于structs。例如,你不能对一个sturct进行枚举:

  1. iex> user = %User{}
  2. %User{name: "jose", age: 27}
  3. iex> user[:name]
  4. ** (Protocol.UndefinedError) protocol Access not implemented for %User{age: 27, name: "jose"}

struct也不同于字典,所以模块``也对它们无效:

  1. iex> Dict.get(%User{}, :name)
  2. ** (ArgumentError) unsupported dict: %User{name: "jose", age: 27}

我们讲在下一章谈到struct如何同协议互动。