檢視原始碼 結構體

我們在 前幾章 學習了映射

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

結構體是建立在映射上的延伸,提供編譯時檢查和預設值。

定義結構體

若要定義結構體,使用 defstruct/1 建構

iex> defmodule User do
...>   defstruct name: "John", age: 27
...> end

defstruct 一起使用的關鍵字清單定義結構體將有哪些欄位及其預設值。結構體採用其定義所在模組的名稱。在上面的範例中,我們定義了一個名為 User 的結構體。

現在,我們可以使用類似於用來建立映射的語法來建立 User 結構體

iex> %User{}
%User{age: 27, name: "John"}
iex> %User{name: "Jane"}
%User{age: 27, name: "Jane"}

結構體提供編譯時保證,只有透過 defstruct 定義的欄位才能存在於結構體中

iex> %User{oops: :field}
** (KeyError) key :oops not found expanding struct: User.__struct__/1

存取和更新結構體

結構體與固定鍵的映射共用相同的語法來存取和更新欄位

iex> john = %User{}
%User{age: 27, name: "John"}
iex> john.name
"John"
iex> jane = %{john | name: "Jane"}
%User{age: 27, name: "Jane"}
iex> %{jane | oops: :field}
** (KeyError) key :oops not found in: %User{age: 27, name: "Jane"}

在使用更新語法 (|) 時,Elixir 知道不會新增任何新鍵到結構體中,讓底層的映射可以在記憶體中共用其結構。在上面的範例中,johnjane 都在記憶體中共用相同的鍵結構。

結構體也可以用於模式比對,用於比對特定鍵的值以及確保比對值與比對值為相同類型的結構體。

iex> %User{name: name} = john
%User{age: 27, name: "John"}
iex> name
"John"
iex> %User{} = %{}
** (MatchError) no match of right hand side value: %{}

結構體在底層是單純的映射

結構體只是具有名為 __struct__ 的「特殊」欄位的映射,其中包含結構體的名稱

iex> is_map(john)
true
iex> john.__struct__
User

但是,結構體不會繼承映射所擁有的任何協定。例如,您既無法列舉也無法存取結構體

iex> john = %User{}
%User{age: 27, name: "John"}
iex> john[:name]
** (UndefinedFunctionError) function User.fetch/2 is undefined (User does not implement the Access behaviour)
             User.fetch(%User{age: 27, name: "John"}, :name)
iex> Enum.each(john, fn {field, value} -> IO.puts(value) end)
** (Protocol.UndefinedError) protocol Enumerable not implemented for %User{age: 27, name: "John"} of type User (a struct)

結構體與協定一起提供 Elixir 開發人員最重要的功能之一:資料多型性。這就是我們將在下一章探討的內容。

預設值和必要鍵

如果您在定義結構體時未指定預設鍵值,則會假設為 nil

iex> defmodule Product do
...>   defstruct [:name]
...> end
iex> %Product{}
%Product{name: nil}

您可以定義一個結構,結合具有明確預設值和隱含 nil 值的欄位。在這種情況下,您必須先指定隱含預設為 nil 的欄位

iex> defmodule User do
...>   defstruct [:email, name: "John", age: 27]
...> end
iex> %User{}
%User{age: 27, email: nil, name: "John"}

反向執行此操作會產生語法錯誤

iex> defmodule User do
...>   defstruct [name: "John", age: 27, :email]
...> end
** (SyntaxError) iex:107: unexpected expression after keyword list. Keyword lists must always come last in lists and maps.

您也可以透過 @enforce_keys 模組屬性來強制執行在建立結構時必須指定某些鍵

iex> defmodule Car do
...>   @enforce_keys [:make]
...>   defstruct [:model, :make]
...> end
iex> %Car{}
** (ArgumentError) the following keys must also be given when building struct Car: [:make]
    expanding struct: Car.__struct__/1

強制執行鍵提供了一個簡單的編譯時期保證,以協助開發人員建立結構。它不會在更新時強制執行,也不會提供任何形式的值驗證。