Skip to content

misleading Structs documentation #13664

Closed
@DenysGonchar

Description

@DenysGonchar

Elixir and Erlang/OTP versions

1.17.0

Operating system

any

Current behavior

Currently, the documentation suggests updating structures using the map syntax::

%{john | name: "Jane"}

This approach has a significant drawback, struct integrity checking is performed at runtime. At best (and only if the code hints that the variable is a structure), the compiler issues a warning if there is a typo in the field name:

Interactive Elixir (1.15.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> defmodule User do
...(1)>   defstruct name: "John", age: 27
...(1)> end
{:module, User,
 <<70, 79, 82, 49, 0, 0, 7, 248, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 247,
   0, 0, 0, 22, 11, 69, 108, 105, 120, 105, 114, 46, 85, 115, 101, 114, 8, 95,
   95, 105, 110, 102, 111, 95, 95, 10, 97, ...>>, %User{name: "John", age: 27}}
iex(2)> defmodule TestModule1 do
...(2)>   def some_function(user) do
...(2)>     %{user | non_existing_field: :some_value}
...(2)>   end
...(2)> end
{:module, TestModule1,
 <<70, 79, 82, 49, 0, 0, 5, 244, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 226,
   0, 0, 0, 21, 18, 69, 108, 105, 120, 105, 114, 46, 84, 101, 115, 116, 77, 111,
   100, 117, 108, 101, 49, 8, 95, 95, 105, ...>>, {:some_function, 1}}
iex(3)> TestModule1.some_function(%User{})
** (KeyError) key :non_existing_field not found
    iex:4: TestModule1.some_function/1
    iex:3: (file)
iex(3)> defmodule TestModule2 do
...(3)>   def some_function(%User{} = user) do
...(3)>     %{user | non_existing_field: :some_value}
...(3)>   end
...(3)> end
warning: undefined field "non_existing_field" in expression:

    # iex:5
    %{user | non_existing_field: :some_value}

expected one of the following fields: __struct__, age, name

where "user" was given the type %User{} in:

    # iex:4
    %User{} = user

Conflict found at
  iex:5: TestModule2.some_function/1

{:module, TestModule2,
 <<70, 79, 82, 49, 0, 0, 6, 16, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 236,
   0, 0, 0, 21, 18, 69, 108, 105, 120, 105, 114, 46, 84, 101, 115, 116, 77, 111,
   100, 117, 108, 101, 50, 8, 95, 95, 105, ...>>, {:some_function, 1}}
iex(4)> TestModule2.some_function(%User{})
** (KeyError) key :non_existing_field not found
    iex:5: TestModule2.some_function/1
    iex:4: (file)

however, if we use the struct syntax for update, the validation is done at compile time:

iex(4)> defmodule TestModule3 do
...(4)>   def some_function(user) do
...(4)>     %User{user | non_existing_field: :some_value}
...(4)>   end
...(4)> end
error: unknown key :non_existing_field for struct User
  iex:6: TestModule3.some_function/1

** (CompileError) iex: cannot compile module TestModule3 (errors have been logged)
    (elixir 1.15.4) src/elixir_module.erl:182: anonymous fn/9 in :elixir_module.compile/7
    iex:4: (file)

Expected behavior

The documentation should recommend the most error-proof method of updating structures:

%User{john | name: "Jane"}

The documentation should also provide a justification of this choice. It might be worth mentioning this in the code anti-patterns chapter as well.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions