Elixir for Ruby developers: the three most important differences
Tags: elixir, ruby
Published: 2023-08-20 07:37:50

**This article is part of a series of extracts from [*Phoenix on Rails*](https://phoenixonrails.com), 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](https://phoenixonrails.com)!**

Many developers who learn Elixir come from a background in Ruby. This is hardly surprising: Elixir's creator, José Valim, was formerly a prominent Ruby developer, and Elixir has been attracting Rubyists since the very beginning!

But while Elixir's syntax looks like Ruby at a glance, you'll quickly realise that these similarities are skin-deep. The two languages are very different in their underlying designs, and Elixir often requires you to structure your code in a way that looks nothing like the equivalent Ruby. Certain aspects of Elixir will feel very unfamiliar to a Rubyist, and will take some getting used to.

This series will give a brief introduction to Elixir for developers who already know Ruby. But before we get into the details of Elixir's syntax, it's worth reviewing some of the higher-level differences between the two languages. In particular, here are the **three most important differences** between Elixir and Ruby:

* Elixir is **functional**. Unlike Ruby, it's **not object-oriented** and has no concept of "classes" or inheritance.
* Everything in Elixir is **immutable**.
* Elixir is **compiled** while Ruby is **interpreted**.

Let's look at those three points in more detail…

## Elixir is Functional, not Object-Oriented

The most important difference between Elixir and Ruby is that **Elixir is not object-oriented**.

Elixir has no classes. You don't initialize "objects" with instance methods. Instead, you write **functions** that are grouped into **modules**. For this reason, Elixir code can be structured very differently to the equivalent Ruby.

But to be clear, Elixir still has *types*, like strings, integers, floats, and lists. It's just that these datatypes don't work in the object-oriented way that you're used to.

To illustrate: in Ruby, strings like `"this"` are **instances** of the class `String`. That class has various **instance methods** like `upcase`, `reverse`, or `length` which you can call on a particular string using `.`-syntax. Open `irb` in a terminal and try it:

```ruby
irb> name = "Pikachu"
irb> name.upcase
"PIKACHU"
irb> name.reverse
"uhcakiP"
irb> name.length
7
```

In Elixir, `String` isn’t a class, because Elixir doesn't have classes. Instead, `String` is a **module** - a collection of **functions** that apply to a string. So instead of writing `name.upcase` you write `String.upcase(name)`.

`upcase` isn't a method on strings (Elixir doesn't have methods). It's a function that takes a string as its first argument.

Follow the [official instructions](https://elixir-lang.org/install.html) to install Elixir if you don't have it already. Then open another terminal window and start `iex`, the Elixir equivalent of `irb`. Here’s how the `String` operations we saw above are performed in Elixir:

```elixir
iex> name = "Pikachu"
iex> String.upcase(name)
"PIKACHU"
iex> String.reverse(name)
"uhcakiP"
iex> String.length(name)
7
```

(Note that you exit `iex` by pressing `Ctrl + C` twice, whereas to exit `irb` you press `Ctrl + D` once.)

Elixir has modules like `Integer`, `List` and `Float`, each containing functions to apply to their respective types:

```elixir
iex> List.first([1,2,3])
1
iex> Float.floor(3.14)
3.0
iex> Integer.digits(90210)
[9, 0, 2, 1, 0]
```

If a function takes more than one argument, normally the **first** argument has the same type as the module. See for example these two ways of testing whether a regex matches a string (regexes in Elixir are written with `~r/…/`):

```elixir
iex> Regex.match?(~r/se[0-9]en/, "se7en")
true
iex> String.match?("se7en", ~r/se[0-9]en/)
true
```

Both these functions test whether a string matches a regex, returning a boolean. The only difference is the order of the arguments: `Regex.match?` takes the regex first, `String.match?` takes the string first. That's the general rule: the name of the module tells you the type of the first argument.

To a Ruby developer, the functional style might feel weird at first. You'll find yourself reaching for instance methods that don't exist, and being initially unsure how to structure something in Elixir when you know how you'd write it in Ruby. But with time the functional style will start to feel natural, and you'll see how it leads to clear, elegant and maintainable code.

## Immutability vs Mutability

The next major difference between Elixir and Ruby is that **in Elixir, everything is immutable**.

In Ruby it's usually possible to mutate objects, which is just a fancy way of saying "change them". For example, the following Ruby creates a string then mutates it in-place:

```ruby
str = "hello"
str.capitalize!
str << "!"
puts str
# Hello!
```

The variable `str` still points to the same memory location, but the contents of that memory have mutated from `"hello"` to `"Hello!"`.

The disadvantage of mutability is that it makes your code unpredictable. When you pass a string to a function, you don't necessarily know what happens to it. The function could mutate your string without you realising, causing unexpected behavior further down the line.

Not everything in Ruby is like this. For example, symbols like `:foobar` are **immutable**. They can't be changed after they've been created. The only way to "capitalize" `:foobar` is to create an entirely new symbol, `:FOOBAR`, at a new memory location.

In Elixir, everything behaves like a Ruby symbol. There's no equivalent of `capitalize!` on a Elixir string because Elixir can't mutate the string in place. You can only use `String.capitalize` (which we saw above), which returns an entirely new string, leaving the original one unchanged.

We'll see in the lessons ahead how Elixir's immutability makes for predictable, reliable and maintainable code.

## Compiled vs Interpreted

Elixir is a **compiled language**. Elixir files end in `.ex`, and get compiled to BEAM files with extension `.beam`. These compiled files are ultimately what gets run when you execute your code.

Try it yourself by creating a file called `math.ex`. (Note that Elixir's `IO.puts` is the equivalent of Ruby's `puts`):

```elixir
defmodule Math do
  def add(a, b) do
    a + b
  end
end

result = Math.add(2, 2)
IO.puts(result)
```

Compile and run the code with `elixirc`:

```bash
$ elixirc math.ex
4
```

This created a compiled file called <code class="inline">Elixir.Math.beam</code> in the current directory. <code class="inline">.beam</code> files are run on <a href="https://www.erlang.org/blog/a-brief-beam-primer/">BEAM</a>, the same virtual machine that's used to run <b>Erlang</b>, a veteran functional programming language that's been around since the ’80s.

(If you’re familiar with the Java ecosystem: Elixir and Erlang both run on BEAM in the same way that Java, Scala and Kotlin all run on the JVM.)

`.beam` files are binary files that you shouldn't edit directly. Make sure to exclude them from your version control system.

BEAM, AKA the Erlang VM, is widely used in telecoms and is renowned for its speed, scalability and fault tolerance - all good qualities for a modern web app!

Elixir might look like Ruby on the surface, but its underlying design is heavily based on Erlang. In fact it wouldn't be too far off to describe Elixir as "Erlang with Ruby syntax".

As well as the `elixirc` command, there's also `elixir`, which compiles and runs your code but doesn't save a `.beam` file to disk. Try creating a one-line file called `hello_world.exs`. (Note the different ending, `.exs` instead of `.ex`. The extra `s` stands for "script".)

```elixir
IO.puts("Hello, world!")
```

Now run it using `elixir` instead of `elixirc`:

```bash
$ elixir hello_world.exs
Hello, world!
```

This time around, no `.beam` file was created. `elixir` compiles your code just like `elixirc`, but it drops the compiled output once its done with it and doesn't save it. `.exs` files are useful for scripting or testing.

By convention we use the `.ex` extension for files we intend to compile to disk with `elixirc`, and `.exs` for files we intend to run as "one-off" scripts we intend to run with `elixir`. This isn't mandatory - you can use `elixir` on an `.ex` file or `elixirc` on an `.exs` file if you want. But it's good to stick to convention.

In practice you'll rarely need to run the `elixir` or `elixirc` commands directly when working on an Elixir (or Phoenix) project. Most of the time you'll run or compile Elixir code using `mix`, Elixir's build tool, as we'll see throughout the course. (For more on what happens when you run `elixir` or `elixirc`, see [this article](https://medium.com/@fxn/how-does-elixir-compile-execute-code-c1b36c9ec8cf), but it's not important for now if you're a beginner.)

With that out of the way, we can start talking about syntax. What does Elixir code actually look like? We'll see in the next extract.