1. 14
  1.  

  2. 9

    There is one more, huge difference. Adding new field to record is breaking change while adding new field to struct is not. This is quite important in situations like:

    • Process state update, with record you need to do it in 2 steps - create new record, migrate the old record to new one, rename new record. With maps it is not a problem, as old pattern matching will still apply to the new map.
    • You need to know the record structure before you can match on it. It adds additional compilation dependency as well as forces you to recompile all modules that were using given record on each update. Map-backed structures do not have that issue.
    1. 1

      No. Adding a new field to a record or struct requires re-compiling code. Struct definitions cannot be changed at runtime. You can get around this limitation by manually adding keys to the map at runtime, but this won’t work if you then try to use the struct to access the new field. Take for example this struct from the blog post:

      defmodule Person do
        defstruct [
          :name,
          # This expression is evaluated at compile time
          created_at: DateTime.now!("Etc/UTC")
          ]
      end
      
      # in iex
      iex(1)> %Person{foo: "test"}
      ** (KeyError) key :foo not found
          expanding struct: Person.__struct__/1
          iex:2: (file)
      
      iex(2)> person = Map.put(%Person{}, :foo, :baz)
      %{
        __struct__: Person,
        created_at: {:ok, ~U[2021-07-06 23:38:24.658023Z]},
        foo: :baz,
        name: nil
      }
      iex(3)> %Person{foo: binding} = person
      ** (CompileError) iex:5: unknown key :foo for struct Person
      
      # See more examples from this page https://elixir-lang.org/getting-started/structs.html
      

      By manually adding a field to the map, you are bypassing some of the things that make record/structs valuable. After adding injecting a new key and value like this, the map no longer matches the struct definition, and any typespecs or types that reference the struct are now wrong.

      Process state update, with record you need to do it in 2 steps - create new record, migrate the old record to new one, rename new record. With maps it is not a problem, as old pattern matching will still apply to the new map.

      If you want to add/change/remove fields from a record or struct at runtime you should migrate your data. OTP behaviors have callbacks for upgrading internal state not only because it’s needed but because you should be explicit about it.

      You need to know the record structure before you can match on it. It adds additional compilation dependency as well as forces you to recompile all modules that were using given record on each update. Map-backed structures do not have that issue.

      Only if you treat them as maps. If you treat them as structs recompilation will always be needed.

    2. 2

      I am a huge fan of using records in Elixir, but then again I’m using it for weird mathy things.

      The pattern matching, automatic typespec work, and various other things make them super pleasant IMHO–especially for GenServer state (provided you don’t, as @hauleth points out, do anything too exotic with hot code updating).