Hello World with Erlang + Rebar3

Have you been hearing about Erlang and want to give it a try? Confused by all the different documentation that is now out of date? Well you have come to the right place. When I decided to do some experiments with Erlang I found it a bit confusing on how to even run an Erlang program. Turns out that is a little bit of a nuanced question in Erlang land. I like using hello world to sanity check new project setups. This post will go through more ways of saying "Hello, World!" then you ever cared to know. If you are interested how to use rebar3's builtin project structures you might find this post quite useful.

What You Will Need

You will need two pieces of software: erlang and rebar3. The erlang package will be the Erlang VM, standard library, and applications. This will include the erl tool.

rebar3 is a build tool for Erlang applications. You will want to use this if you need to install dependencies, use the OTP framework, and have a good time. Make sure you are using rebar3 and not rebar.

My recommendation is to follow along with the rebar3 Getting Started guide.

Hello World - REPL

The easiest way to do hello world in Erlang is to start an Erlang shell. You can think of it like a REPL but for Erlang code. Erlang's shell is quite powerful, and I recommend incorporating it into your workflow. Start an Erlang shell like this:

erl

This will open a prompt:

Eshell V10.7.1  (abort with ^G)
1>

We print "Hello, World!" to the screen:

1> io:format("Hello, World!~n").
Hello, World!
ok
2>

Above, we used the format function in the io module to print some text. This module is part of the Erlang standard library. The '~n' is an Erlang way of denoting the newline character Or else 'ok' would've been shmushed on the line above. The format function can take more than one argument. Doing neat things like this:

2> io:format("Hello, ~s~n", ["World!"]).
Hello, World!
ok
3>

What happened there is the format function substituted '~s' for "World!". Exactly like how it substitutes '~n' for '\n'.

Hello World - Escript

Now we are going to learn how to print "Hello, World!" to the terminal like you would expect with languages like C or Java.

We will now to using rebar3 instead of erl for creating and running our Erlang apps. One of the nice things about rebar3 is that it includes project templates. We can use it for this next step.

rebar3 new escript hello_cli
cd hello_cli

We will now be working in the hello_cli project directory for this. What we have done is create an escript project with this structure:

.
├── LICENSE
├── README.md
├── rebar.config
└── src
   ├── hello_cli.app.src
   └── hello_cli.erl

This creates a src directory and a few basic files. The file of interest for us is src/hello_cli.erl. The generated file should look like this:

-module(hello_cli).

%% API exports
-export([main/1]).

%%====================================================================
%% API functions
%%====================================================================

%% escript Entry point
main(Args) ->
    io:format("Args: ~p~n", [Args]),
    erlang:halt(0).

%%====================================================================
%% Internal functions
%%====================================================================

Using this function alone we can get a version of Hello World to print. We just learned about how io:format works so we will take advantage of that. All we have to do now is build the app. We do this with the escriptize command.

rebar3 escriptize

Now a built binary will be located at _build/default/bin/hello_cli

We can run it like this:

./_build/default/bin/hello_cli Hello, World!
Args: ["Hello,","World!"]

We got a hello world for free just with the default rebar3 template!

Hello World - Library

At this point we have covered the conventional notion of hello world. We will be moving on to using "Hello, World!" to test out more advanced Erlang project structures.

What if we wanted to make a library that provided a function to print "Hello, World!". That way when new Erlang developers come around they can use this module to print "Hello, World!" more easily! ;)

We can start by making a new project with rebar3.

rebar3 new library hello_erl
cd hello_erl/

Inside of the hello_erl project you will see:

.
├── LICENSE
├── README.md
├── rebar.config
└── src
   ├── hello_erl.app.src
   └── hello_erl.erl

The file of interest once again being the erl file in the src/ directory.

We want to make it look like this:

-module(hello_erl).

-export([hello/0]).

hello() ->
    io:format("Hello, World!~n").

So we've made our simple module. However, how do we run this? Well remember when we started the Erlang shell with erl?

We can do the equivalent with rebar:

rebar3 shell

Most Erlang documentation will assume you know this already. I found this a bit confusing at first. However, being a fan of LISP I don't shy away from a good REPL. We can use this REPL to call our hello function inside of the hello_erl module.

===> Verifying dependencies...
===> Compiling hello_erl
Eshell V10.7.1  (abort with ^G)
1> hello_erl:hello().
Hello, World!
ok
2>

This is a really nice way to test out your module or other libraries during development. We will be extending this idea in different ways with the next sections.

Hello World - App

When using rebar3 you may have noticed the app command. You thought to yourself, Aha! Here is the template I need for my new awesome web server app. You will be wrong.

I did this. I was wrong. The reason for this misunderstanding is that App in Erlang means something different from the current mainstream notion of an App. The Erlang team came up with this name long before the invention of the iPhone and dotcom boom. Dave Thomas suggests thinking of them like components.

In reality an Erlang App is just a library, or module, with state. When you see state, especially shared state, think process. An application in Erlang is a process wrapping state with a module. I like to think of the Erlang VM as the cloud, and the applications (processes) being like VMs on that cloud. You might be thinking, I thought hello world was just a side effect. Why do we need state? We don't. We actually only need the "Hello, World!" to learn how these Erlang application processes behave.

We begin this part by making a new project with the app template:

rebar3 new app hello_world
cd hello_world

The project directory will look like this:

.
├── LICENSE
├── README.md
├── rebar.config
└── src
   ├── hello_world.app.src
   ├── hello_world_app.erl
   └── hello_world_sup.erl

But wait, now there are two erlang files in the src directory? What is 'sup'? That stands for supervisor. This is a concept from OTP, which is Erlang's standard framework for making processes and complex configurations of processes. We don't have to worry about that file. We simple need to edit hello_world_app.erl to look like this:

%%%-------------------------------------------------------------------
%% @doc hello_world public API
%% @end
%%%-------------------------------------------------------------------

-module(hello_world_app).

-behaviour(application).

-export([start/2, stop/1]).

start(_StartType, _StartArgs) ->
    io:format("Hello, World!~n"),
    hello_world_sup:start_link().

stop(_State) ->
    ok.

%% internal functions

The only addition being the line to include the call to print our beloved "Hello, World!". Notice that it is inside of a function called start. And below that we see the supervisor again. This start function is called when the application starts. Which we can do just by starting the Erlang shell.

rebar3 shell

Output should be similar to this:

===> Verifying dependencies...
===> Compiling hello_world
===> The rebar3 shell is a development tool; to deploy applications in production, consider using releases (http://www.rebar3.org/docs/releases)
Hello, World!
===> Booted hello_world
Eshell V10.7.1  (abort with ^G)
1>

What happened here?! Well when the Erlang shell got booted up, it automatically called the start function of the helloworldapp module. Which is where we included our "Hello, World!" call. In more detail, what happens is that the shell boots up, starts the application process with the start function.

So how do I setup my project if I wanted to make a super cool cowboy webserver that I've been hearing so much about? I won't show you how to get started with cowboy. There is a great guide for that here.

What I will show you, in the next section, is the project structure you will need.

Hello World - Release

For creating an application in the mainstream sense of the word, we use the release template. An alias for this is umbrella.

rebar3 new release hello_erlang
cd hello_erlang

The project structure will look like this:

.
├── apps
│  └── hello_erlang
│     └── src
│        ├── hello_erlang.app.src
│        ├── hello_erlang_app.erl
│        └── hello_erlang_sup.erl
├── config
│  ├── sys.config
│  └── vm.args
├── LICENSE
├── README.md
└── rebar.config

This is quite similar to the app template, but actually allows for multiple apps inside of it. Hence the new apps directory. We will see how to make use of that in a bit. To get a hello world we actually do the same thing we did with the app template. This time changing the file at apps/helloerlang/src/helloerlang_app.erl.

Give that a spin to prove to yourself that it works. Now we are going to really blow your socks off. We are going to use the rebar3 app template inside of the hello_erlang project directory! Then we will have two apps! You don't have to do this for your projects, but you might prefer it if you are making a majestic monolith.

cd apps/
rebar3 new app hello_mars

Which will make a new directory in apps called hello_mars. You will notice it has its own license and README. I thought that was weird at first. After thinking about I realized that is quite helpful if you want to package the application separately from the app. If you don't need that stuff then you can delete it.

Once you generate the app template the apps directory should resemble this:

.
├── hello_erlang
│  └── src
│     ├── hello_erlang.app.src
│     ├── hello_erlang_app.erl
│     └── hello_erlang_sup.erl
└── hello_mars
   ├── LICENSE
   ├── README.md
   ├── rebar.config
   └── src
      ├── hello_mars.app.src
      ├── hello_mars_app.erl
      └── hello_mars_sup.erl

Edit hellomars/src/hellomars_app.erl to look like this:

%%%-------------------------------------------------------------------
%% @doc hello_mars public API
%% @end
%%%-------------------------------------------------------------------

-module(hello_mars_app).

-behaviour(application).

-export([start/2, stop/1]).

start(_StartType, _StartArgs) ->
    io:format("Hello, Mars!~n"),
    hello_mars_sup:start_link().

stop(_State) ->
    ok.

%% internal functions

The next thing we need to do is in the root of our hello_erlang project, edit the rebar.config to look like this:

{erl_opts, [debug_info]}.
{deps, []}.

{relx, [{release, {hello_erlang, "0.1.0"},
         [hello_erlang,
          hello_mars,
          sasl]},

        {sys_config, "./config/sys.config"},
        {vm_args, "./config/vm.args"},

        {dev_mode, true},
        {include_erts, false},

        {extended_start_script, true}]
}.

{profiles, [{prod, [{relx, [{dev_mode, false},
                            {include_erts, true}]}]
            }]
}.

What we did was we added hello_mars module to the relx config. Now when we start up the Erlang shell it should look like this:

===> Verifying dependencies...
===> Compiling hello_mars
===> Compiling hello_erlang
Eshell V10.7.1  (abort with ^G)
1> ===> The rebar3 shell is a development tool; to deploy applications in production, consider using releases (http://www.rebar3.org/docs/releases)
1> Hello, World
1> Hello, Mars!
1> ===> Booted hello_erlang
1> ===> Booted hello_mars
1> ===> Booted sasl
1> 

To really shake things up we can use the hellomars as a dependency of helloerlang by making the hello_erlang.app.src file look like this:

{application, hello_erlang,
 [{description, "An OTP application"},
  {vsn, "0.1.0"},
  {registered, []},
  {mod, {hello_erlang_app, []}},
  {applications,
   [kernel,
    stdlib,
    hello_mars
   ]},
  {env,[]},
  {modules, []},

  {licenses, ["Apache 2.0"]},
  {links, []}
 ]}.

We haven't needed to touch this file before. Usually you wont. If you want to do advanced configurations of your Erlang applications this is the spot to do it.

NOTE: Make sure to remove hello_mars from the root rebar.config file we just did before you restart the shell!

When you boot up the shell you will see the hellomars application starts before the helloerlang application.

===> Verifying dependencies...
===> Compiling hello_mars
===> Compiling hello_erlang
===> The rebar3 shell is a development tool; to deploy applications in production, consider using releases (http://www.rebar3.org/docs/releases)
Eshell V10.7.1  (abort with ^G)
Hello, Mars!
1> Hello, World
1> ===> Booted hello_mars
1> ===> Booted hello_erlang
1> ===> Booted sasl
1> 

How neat! The Erlang runtime knows to start hellomars before helloerlang because it is listed as a dependency. Most of the time I don't think you won't need this much power, but it is nice to have when organizing complex projects.

Conclusion

In this short post we've actually covered a lot of topics you will come across when working with Erlang. And we have only scratched the surface.

We've seen how to do simple to complex hello worlds, how to use the Erlang shell, and how to use rebar3 to get started with idiomatic Erlang/OTP applications.

By now, you might be confused about what project template you should use. My recommendation is that most of the time you will want the release template.

The rebar3 docs have a great choice matrix in their docs that I recommend checking out if what you are thinking about doesn't fit in the standard (mainstream) application box.

Best of luck and Erlang on!

More Posts