tim2CF
Hello!
Does anyone know why Elixir structures are implemented on top of Erlang maps, but not on top of Erlang records (tuples)? For me, Erlang records are looking more natural then maps for basement of Elixir structs, and besides in some cases it seems performance of records is better (for example in pattern matching), take a look:
iex(2)> defmodule Hello do
...(2)> defstruct [:foo]
...(2)> end
{:module, Hello,
<<70, 79, 82, 49, 0, 0, 5, 144, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 182,
0, 0, 0, 18, 12, 69, 108, 105, 120, 105, 114, 46, 72, 101, 108, 108, 111, 8,
95, 95, 105, 110, 102, 111, 95, 95, 7, ...>>, %Hello{foo: nil}}
iex(3)> struct = %Hello{foo: 123}
%Hello{foo: 123}
iex(4)> fn -> 1..1000000 |> Enum.each(fn(_) -> %Hello{foo: foo} = struct; foo end) end |> :timer.tc
{13719674, :ok}
iex(5)> record = {Hello, 123}
{Hello, 123}
iex(6)> fn -> 1..1000000 |> Enum.each(fn(_) -> {Hello, foo} = record; foo end) end |> :timer.tc
{565886, :ok}
iex(7)>
Of course, you can say - “If you like Erlang records, just use it”, but Elixir infrastructure is very coupled with structs/maps - they are used everywhere (Ecto, Phoenix, Plug, Elixir standard libraries). So if I’m using Elixir, I don’t really have a choice - I have to use structs/maps
Trending in Discussions
Hey there,
It’s been more than a year since we started using LiveView as our main UI library and building a whole library of UI componen...
New
I am happy to introduce the very α version of the new programming language compiled to BEAM.
Welcome Cure.
It has literally three kille...
New
Hi everyone!
The first release candidate for the Expert language server project is now available!
We’ve published a press release detai...
New
A little off-topic, but I feel like people here have a good head on their shoulders.
I used to be quite good at making software. Was luc...
New
I’ve been using Emacs as my main code editor for more than a two years. It’s a custom build version although I’ve tried doom emacs and sp...
New
Hi there! :wave:
@frigidcode and I (but mostly him) have been running an Elixir Book club, we’re almost done with Designing Elixir Syste...
New
I love Elixir. It’s one of 2 programming languages I’ve ever fallen in love with.
But I don’t use it anymore.
Serverless was the promis...
New
Other Trending Topics
Edit: 2026 May 15 - This post is archived.
Mob is alive!!
Main docs: mob v0.7.11 — Documentation
A bit of explanation for the slightly c...
New
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
Hobbes is a low-level distributed database for the Elixir programming language.
Hobbes provides a simple, safe, and scalable storage lay...
New
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Just published claude-code-elixir, a plugin marketplace for Claude Code with Elixir support. These are the plugins I’ve been using for my...
New
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ai
- #ecto-query
- #elixirconf-us
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #elixirconf-eu
- #api
- #forms
- #metaprogramming
- #hex










Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
kokolegorille
There is a good answer from Jose here about the choice of struct vs record.
michalmuskala
Please, please, don’t benchmark in the shell. The shell runs an interpreter and not the compiled code. The result will probably will be wildly different.
OvermindDL1
Indeed, here it is in Benchee, I tried to prevent certain optimizations from happening by interning the test data in different modules than that which is accessed so things like the record macro’s don’t get optimized out and so forth (records were a lot faster than structs before I made that change).
Code
struct_record_bench.exs:Results:
So Records are faster in general (in all tested cases here actually) than Structs, but only marginally so, so much so that only the most performance sensitive code would really care, so in general most people shouldn’t care.
I’m surprised that putting the struct type in the update syntax doesn’t make it faster actually as it could infer some existing structure, but I guess that all it would be doing is adding an extra runtime check or so (hence the module-optional variants are slower in the end).
EDIT: Personal Opinion time: Personally I’d prefer records were ubiquitous and used struct syntax (first class records in other words). Records in every language I’ve seen are statically sized, there is no point in them being maps, especially if they ‘own’ their module definition as structs do now then all the proper accessors for Access and extra data would all be accessible as they are for structs as well and as such by using those generated macro’s then you could generate getting/setting code that would be even more efficient than how structs work now. HOWEVER, Elixir is extremely poorly typed and doesn’t know what the type of a given thing would be, and Erlang works around that by requiring using the record name at all uses of a record variable, Elixir tries to be a little more succinct, and that succinctness is at odds with efficiency, and so the first-class syntax uses the slightly less efficient version in order for ease of use and relegates the more efficient version to a side set of macro’s since you require the names anyway. If Elixir had a decent typing system then you’d be able to have both efficiency and succinctness, but maybe that’s for an Elixir 2.0 or something. ^.^
EDIT: Hmm, a possible workaround for the first-class syntax access would be just dispatching based on the ‘module’ in the type-tag of the record, it would be a ‘remote call’ on the BEAM but might be good… I should test…
Qqwy
Great benchmark!
In practice, wouldn’t the datastructure’s internals usually be read/patternmatched/created/modified from within the same module that defines it? So is preventing these optimizations justified?
OvermindDL1
In some cases sure, in those cases though then records are even much faster than structs (still not enough for ‘most’ code to care though).
OvermindDL1
I went ahead and decided to test a more ‘direct’ record interface, I added a new Record interface to emulate the back-end of if a front-end syntax were used (the start of it anyway, it could be more fleshed out).
The
struct_record_bench.exsfile now:Essentially the ‘stock’ is the Elixir stock record interface, the ‘direct’ is what an optimized type-aware setup would do (ala requiring the use of the record ‘name’ in the usages, like in Erlang), and the ‘remote’ is what a dynamic dispatch interface would look (so something like elixir’s existing struct syntax with no known type information). And the results:
So first of all, the stock and direct should be identical if I apply all optimizations to the direct version as stock already has, even without it (a single opcode change from the looks of it, meh right now, close enough) it’s still almost identical. As for
directit is doing a remote (I.E. slow) module function call (ah if only we had tuple calls still to save the uglyelem’ing stuff on the call, then we really could “right now” have a native looking syntax, but tuple calls got broke in the latest OTP version, permanently…), so it should be slower in general and so it is. Some of these calls were slower on older BEAM’s so that is probably why map’s were picked when maps came out for ‘structs’ but nowadays I’m not sure if there is a reason to having picked maps over tagged tuples for records as in the traditional cases you still put the type for 'put’ing%StructName{..|..}as well as could easily come up with something similar for optimized ‘get’ call). You still have unknown-record fallback capabilities as well with this even if a touch slower.Still, it was a fun test, I really do wish Elixir Structs were tagged tuples underneath as it would make working in the Erlang ecosystem easier. ^.^
michalmuskala
The primary advantage of structs over records is readability. I can take any struct, print it anytime and it will be readable. With records, you can easily get an unreadable pile of nested tuples - it’s a huge pain when working with Erlang APIs. Another huge disadvantage of records is during upgrades - changing the record structure correctly is extremely hard when you have the “new” modules with the new definition of the records that need to handle the old records. For that reason, I know that some Erlang codebases don’t use records at all, but rather prefer proplists, which are a poor-man’s version of maps.
Qqwy
To be honest, I think the readability of map-based structs is greatly improved because of Elixir’s syntactic sugar that turns
%{a: 1, b: 2, __struct__: Foo}into%Foo{a: 1, b: 2}. But of course even without that you are able to see the names of the different fields; is this what you mean?As for upgrading: This is definitely a place where map-based structs are better than tuple-based records!
OvermindDL1
That’s why you have an
inspectfor them. Unlike the older Elixir days pre-consolidation (and my ProtocolEx can handle tagged tuples fine and faster than Elixir’s Protocols) the protocol can know precisely what is supported and what is not and such a default implementation could even by specified by thedefbetterrecordcall itself (or via an option in case they want to override it, or add an__inspect__method in the module as a fallback or whatever, lots of options).With my above defined BRecords module you could easily have upgrade functions specified inside it then protocol dispatch an upgrade path along them (and other things if needed), or just call the functions straight to upgrade them. You have to do similar things to maps and structs anyway as data formats change, a string might need to become a list in whatever field, etc… etc… It is good to reify those to a specific area and just pass in the needed upgrade information as always. I never had issues with that all in Erlang.
I never actually ran across a library that used proplists as their ‘state’ store, records or trivial values I’ve always seen. That sounds like a very bad way to handle the code and would make dialyzer typing it a bit more irritating as well (though with such codebases I wonder if they used dialyzer at all).
You could have identical syntax for records though, that exact same
%Foo{a: 1, b: 2}could easily generate aFoorecord.Still unsure about that, with maps it’s easy to keep old useless data polluting it, more irritating to Dialyze, takes up more space, takes longer to update unless it’s truly huge, and I still think it is a bad BAD idea to upgrade state in-place, you should always decompose an old version and build a new version just to make sure dialyzer helps catch issues (which it would not otherwise on upgrades since it ‘assumes’ the old version types would be the new already unless you explicitly override it), to make sure you don’t miss something (with a map you can forget a key pretty easily, not so with records), among other things.
All of this would of course be far far more sensible and direct if Elixir actually had a half decent type system, dynamic typing is a horror and is the reason why this is an issue at all… >.<
tim2CF
Totally agree, and I hope at least one of these projects will be usable someday..
https://github.com/josefs/Gradualizer
https://github.com/alpaca-lang/alpaca
https://github.com/wende/elchemy
Just off-topic comment because you mentioned type systems