Ruby symbols vs. Elixir atoms - what's the difference?

This article is an edited extract from Phoenix on Rails, a comprehensive guide to Elixir, Phoenix and LiveView for developers who already know Ruby on Rails. If you’re a Rails developer who wants to learn Phoenix fast, click here to learn more!

In the previous extracts we covered the three most important differences between Ruby and Elixir. Then we had an intro to Elixir syntax, including a look at Elixir’s most basic datatypes, like strings, integers and floats.

In this and the coming extracts, we’ll look at some more of Elixir’s basic types. Let’s start with something that should be easy for a Ruby developer to understand: atoms.

Elixir atoms are essentially the same as Ruby symbols. They’re textual values written with a colon, :like_this. They can also be written with double or single quotes, :"like this" or :'like this'.

In Ruby, strings are mutable while symbols are immutable. In Elixir this distinction is irrelevant, because - as we’ve seen - everything is immutable.

Elixir atoms and Ruby symbols serve a similar role - they represent constant values and names. We use them for things like keys in maps (maps are key-value data structures similar to Ruby hashes):

user = %{name: "Jimi", age: 27}

Within quotes, an atom can contain any character. Without quotes, atoms can only contain Unicode characters like letters, numbers, underscores, and @.

That last point is a minor difference from Ruby - Ruby symbols can’t contain @ unless they’re written with quotes.

# Elixir
iex> :george@arrowsmith
# Ruby
irb> :george@arrowsmith
# syntax error
irb> :"george@arrowsmith"

Booleans and nil are atoms

I mentioned true, false and nil in the last extract, but I skipped an interesting detail - these values are really just atoms! That is, true, false and nil are nothing more than syntactic sugar for the atoms :true, :false, and :nil.

iex> :true
iex> :false
iex> :nil

This is totally different from Ruby, where true, false and nil are singleton instances of TrueClass, FalseClass and NilClass respectively, and have no inherent connection with symbols.

It also means that :false and :nil are considered ‘falsey’ in if conditions etc.:

iex> if :false || :nil do
...>   IO.puts("Are either of them true?")
...> else
...>   IO.puts("Nope, both are false!")
...> end
# Nope, both are false!

Modules are atoms

Module names are atoms too. When you create a new module called, say, MyApp.User:

defmodule MyApp.User do
  # module content …

… you’re implicitly creating a new atom called :"Elixir.MyApp.User":

iex> :"Elixir.MyApp.User" == MyApp.User

An Elixir module’s name always implicitly starts with Elixir, although you don’t necessarily have to write it:

iex> Elixir.Integer == Integer
iex> :"Elixir.Integer" == Integer
# When written like an atom, you have to include the "Elixir"
iex> :Integer == Integer

Since Elixir runs on the BEAM (the Erlang VM), we can also access modules from the Erlang STDLIB, whose names are lowercase atoms. We’ll see more of this in a later lesson; for now, here’s an example of using functions from Erlang’s math module:

iex> :math.pi
iex> :math.sqrt(2)
iex> :math.log10(10)

Garbage collection

An important difference between Elixir and Ruby is that in Elixir, atoms are never garbage collected. Once an atom has been created, it will take up memory for as long as your program stays running. In Ruby, symbols are garbage collected (although this has only been true since Ruby 2.2.)

This means that you should never write Elixir code such that atoms can be created from user input. Otherwise, a malicious user could cause your app to crash by creating atoms until the program runs out of memory.

Learn Phoenix fast

Phoenix on Rails is a 65-lesson tutorial on web development with Elixir, Phoenix and LiveView, for programmers who already know Ruby on Rails.

Learn more