Why we chose Elixir over Swift for our server (and why we’d do it again)
How Erlang’s battle-tested runtime and Elixir’s developer experience helped us build Tuist’s server infrastructure faster, simpler, and more reliably than we ever imagined
When we decided we wanted to bring a server to Tuist, we were presented with the decision of which language we should write the server in. At the time we were writing a lot of code in Swift for the CLI, so a natural decision would have been to use Swift on the server too—an area where the community was and continues to be actively invested. We love Swift, and we were familiar with other languages like Ruby and JavaScript, which are traditionally used in web projects. However, at the time we had come across Erlang and Elixir, and we couldn’t think of a better choice for a language and runtime.
If you write Swift, you might wonder what makes Elixir so unique. This is my attempt to distill the things that I like the most and that had a very positive impact on our ability to deliver value to Tuist users.
REPL
Being able to open a console with access to all the symbols and execute code is priceless in development and can be handy at times in production when you might need to debug an issue. This is something that I use every day and that I couldn’t imagine not having access to.
Hot reloading
Coming from Xcode’s slow compilation cycles, having hot reloading feels magical. You change one line of code, and the runtime picks up the changes automatically (with some exceptions). Moreover, due to the functional nature of Elixir, the changes are reconciled gracefully. In development this makes a huge difference in productivity and has tremendously helped us ship features very fast. Erlang also supports hot reloading in production systems, but we never used it. We prefer to go through standard containerized deployments.
LiveView
The most commonly used web framework in Elixir, Phoenix, has an answer for building interactive UIs leveraging the awesomeness of Erlang. It’s called LiveView. It draws inspiration from React and leverages Erlang processes to keep the UI state in the server and automatically calculate the diff and send it over the wire. And why is it awesome? Because you don’t have to deal with client-side state served through an API, like it’s the case in SPAs, nor have to have logic in the server that makes assumptions about the markup on the client, for example Rails’ Hotwire.
We ship UI at the speed of light. The drawback is that there aren’t good UI toolkits out there compared to the ones that are available for JS UI solutions, but we solved that by building and open-sourcing our own, Noora.
Live and collaborative solutions
Erlang processes make it extremely easy to build live and collaborative solutions, which Figma and many other tools have gotten us used to.
We started leveraging that capability for making some of our dashboard live, so the content refreshes automatically as new content comes in, and we plan to leverage it further to build collaborative solutions, like having multiple people testing an app in the browser. Coming up with a similar solution in other ecosystems would require additional layers of indirection, but with Erlang, everything is built into the runtime. It’s awesome.
Ecosystem
People say that Erlang and Elixir’s ecosystem is not as mature as others. I think what they really mean is that it’s not as noisy as others, for example JavaScript’s. We never found ourselves in a position where we lacked a community solution for a need that we had. In fact, quite the opposite—we found many solutions for problems that we’d never imagined had been solved and open-sourced by the community. From solutions to rate limit requests, to ones that define data ingestion pipelines leveraging Erlang primitives for it. Building with the ecosystem’s tools and libraries is such a joy. Everything is well documented, libraries are well maintained, and everyone is very helpful and supportive.
Ease of scale
For a team as small as ours, being able to scale our development and production systems at a low cost is essential. Erlang’s process-based foundation combined with functional programming makes it extremely easy. For instance, you can parallelize test execution with no risk of data races and low risk of race conditions. In other words, if you want to run your tests faster, run them on a machine with more cores and they’ll run faster. In production it’s a similar story. The Erlang VM (BEAM) will make use of all the memory and CPU resources that are available. Since every process is independent CPU and memory-wise, there’s no risk of bad memory access, and thanks to its supervision model you can easily create error boundaries to make the app very resilient to errors.
I love Elixir’s concurrency model where you write concurrent code without thinking about it. Compare that to Swift where the language is suddenly full of annotations and has a concurrency model that’s not trivial to reason about. It might feel like an unfair comparison because Swift had a stateful and Objective-C world to reconcile, but the experience is challenging in my opinion. Swift’s server-side capabilities continue to evolve, and the community is making impressive strides. While it may not match Elixir’s maturity in this space today, there’s real potential for growth as the ecosystem develops.
Closing thoughts
Elixir was a great decision for Tuist, and if we had to make the decision again today, we’d most likely lean toward Elixir again. Thanks to it we can stay focused on what matters most to us—solving the challenges developers present us with. Our stack is simple yet powerful, and we feel very supported by the language, the runtime, and the ecosystem around it. We still love Swift and use it as much as we can. While its server ecosystem and capabilities continue to improve and may match Elixir’s in the future, there’s exciting progress happening, and the foundation is being strengthened with each release.