Hi everyone,
Elixir v1.19.0-rc.0 included a deprecation of the struct update syntax. However, we will ship with a different warning on Elixir v1.19.0-rc.1. We have a poll at the end that will help us decide the final behaviour.
Deprecation of the struct update syntax in Elixir v1.19.0-rc.0
Elixir v1.19.0-rc.0 deprecated the struct update syntax. The struct update syntax was introduced to help us find bugs in the field name:
def set_address_to_earth(user) do
%User{user | adress: "Earth"}
end
The above would check, at compile-time, that āadressā is a valid field and fail to compile in this case.
However, thanks to the type system, we no longer need the struct update syntax, by simply pattern matching on the %User{}
struct, we already get similar guarantees:
def set_address_to_earth(%User{} = user) do
%{user | adress: "Earth"}
end
Not only that, pattern matching helps us find other bugs in the code too. For example, imagine you have this code:
def trim_address(user) do
%User{user | address: String.trim(user.adress)}
end
In this case, there is a typo when reading the field name, which is not caught by the update syntax, but it would have been caught if you pattern matched on the %User{}
struct.
For those reasons, we have decided to deprecate the struct update syntax on v1.19.0-rc.0, and ask developers to use pattern matching instead. Especially because we noticed that several projects were using the update syntax instead of the superior pattern matching:
iex(2)> %URI{uri | path: "/"}
warning: the struct update syntax is deprecated:
%URI{uri | path: "/"}
Instead prefer to pattern match on structs when the variable is first defined and use the regular map update syntax:
%{uri | path: "/"}
āā iex:2
Converting the struct update syntax into type assertions in Elixir v1.19.0-rc.1
Once we released v1.19.0-rc.0, we started hearing some concerns about removing the struct update syntax. In a nutshell, it was pointed out that while the struct update syntax is suboptimal for the type system, it may provide an important hint for readers of the code. Therefore a new proposal was introduced: Elixir should warn if you use the struct update syntax without pattern matching on the struct. In other words, if you wrote:
def trim_address(user) do
%User{user | address: String.trim(user.adress)}
end
Elixir will emit a warning, suggesting you to pattern on %User{}
. Therefore the correct version would be this:
def trim_address(user = %User{}) do
%User{user | address: String.trim(user.adress)}
end
Of course, once you add the pattern matching, you may convert the struct update into a regular map update, if you desire to:
def trim_address(user = %User{}) do
%{user | address: String.trim(user.adress)}
end
Note that Elixir will only emit this warning if it cannot provide at compile-time it is a struct of certain type, working effectively as a type assertion.
The warning has been implemented in the v1.19 branch and it helped confirm that indeed, in many situations, only struct updates were used, and not pattern matching. For example, in Plug, we got a few warnings, such as this one:
warning: a struct for Plug.Conn is expected on struct update:
%Plug.Conn{conn | params: params}
but got type:
dynamic()
where "conn" was given the type:
# type: dynamic()
# from: lib/phoenix/controller.ex:1326:20
conn
when defining the variable "conn", you must also pattern match on "%Plug.Conn{}".
hint: given pattern matching is enough to catch typing errors, you may optionally convert the struct update into a map update. For example, instead of:
user = some_fun()
%User{user | name: "John Doe"}
it is enough to write:
%User{} = user = some_fun()
%{user | name: "John Doe"}
typing violation found at:
āā lib/phoenix/controller.ex:1334:5: Phoenix.Controller.scrub_params/2
To deprecate or not to deprecate, that is the question
While I believe the above is an improvement to Elixir v1.19.0-rc.0, as it forces people to pattern match before we potentially deprecate struct updates, we still need to decide if we want to deprecate the struct update syntax in v1.20 or later.
The argument to deprecate the struct update syntax is to reduce the language surface. If the struct update syntax must only be used alongside pattern matching, it effectively does not add any new guarantees to the language, and therefore there is an argument that it is no longer a useful construct.
The argument against deprecating it is that, while not useful to the compiler, it can be useful for humans. The main argument is that, if you have a long function, %User{user | ...}
is a reminder and a type assertion of the type of the struct, while %{user | ...}
would require you to keep more context in your head.
Therefore, I am reaching out to the community. The goal is to run a non-binding poll now, gathering community feedback, and then do another non-binding poll before v1.20, after people have migrated to v1.19, to understand how they have modified their codebases in practice. Thank you for participating!
Should we deprecate or keep the struct update syntax?
- Deprecate the struct update syntax in favor of pattern matching and map updates
- Keep the struct update syntax as a type assertion