First Step Building Ocaml with Dune

pancy
4 min readJun 11, 2022
Photo by Mike Yukhtenko on Unsplash

This post intends to demystify the process of setting up an Ocaml project with Dune in the hope that it will show up in searches to help attract more developers to stick to Ocaml.

In my opinion, Ocaml is a really great language that strikes a balance between functional, ergonomy, type safety, and simplicity. The website is really nicely designed and updated (kudos to whoever is in charge of that). However, one gripe I have always had with Ocaml is the lack of resources on how to easily set up a buildable project with Dune.

Many great languages take off to mainstream adoption not just because of their intrinsic goodies, but because of, hold your breath, build system. Take Node.js ecosystem’s npm and Rust’s cargo for the cue. They just make it a no-brainer to start new projects time after time.

At the risk of putting off experienced Ocaml users, I hope the beginners who have stumbled on the Dune roadblocks will find some of this useful.

I won’t be getting into how to install Ocaml, dune, and its dependencies, so feel free to follow this official guide and/or this excellent one from Real World Ocaml.

The first thing you need to do to start a project is dune init proj <name> , which will create a project directory for you. This is similar to npm init only you don’t need to create a directory and cd into it like the latter does. Provided your project name you typed in was “simple”, you will see a directory with the following structure:

.
├── bin
│ ├── dune
│ └── main.ml
├── dune-project
├── lib
│ └── dune
├── simple.opam
└── test
├── dune
└── simple.ml

Try running dune exec simple to build a skeleton program bin/main.mlthat prints “Hello, World!”. Open the file and you’ll see the code:

let () = print_endline "Hello, World!"

Inside /bin contains two files — dune and main.ml . The latter is an OCaml “entry point” program (FYI, OCaml has no concept of the main entry point function like Rust or C, so the naming is just arbitrary). We will come back to look at the dune file, which is the bread and butter of the Dune build system.

Now, inspect the dune-project file:

(lang dune 3.2)(name simple)(generate_opam_files true)(source
(github username/reponame))
(authors "Author Name")(maintainers "Maintainer Name")(license LICENSE)(documentation https://url/to/documentation)(package
(name simple)
(synopsis "A short synopsis")
(description "A longer description")
(depends ocaml dune)
(tags
(topics "to describe" your project)))

In order to grapple with dune, you’ll need to understand the concept of package. A package is a collection of executables, libraries, and other files. Because linking Ocaml files together is such a nightmare, you will want to “attach” them to a package and let dune build it for you.

The package stanza (a fancy word for the S-expression within the pair of parentheses) declares a package for your project. That’s how dune knew to print “Hello, World!” as you typed dune exec simple . It was building and running the package by its name. If there’s only one package in a project, the package needs to be named the same as the project name (in this case simple ).

The more interesting part is the dune file in the /bin directory:

(executable
(public_name simple)
(name main)
(libraries simple))

The executable stanza in this dune file makes sure whatever is within this /bin directory will link up and become a single executable (i.e. command-line callable). public_name stanza attaches this executable to the simple package declared in the main dune-project file. This public_name can be anything you want your user to be able to type and call the executable. The stanza only has to exist for this executable to be attached to the package simple .

You can have more than one package declared in the dune-project file provided you added an extra package stanza to the dune file for any executable so Dune knows which package you’re attaching to.

Now what we will try to do is to refactor the printing statement into a function inside a module in /lib directory, and have the main executable at /bin/main.ml call the function. Edit the /bin/dune file to be the following:

;; Implicitly attached to "simple package"
(executable
(public_name greeter) ;; Change this name
(name main) ;; Name of the file e.g. main.ml
(libraries hello)) ;; Library to connect to

Then open the file /lib/dune and modify to the following:

(library
(name hello)
(public_name simple.hello)) ;; Required to define in the simple namespace

Add a file name /lib/hello.ml with a single function hello_name :

open Printflet say_hello name = printf "Hello, %s!\n" name

Now, go ahead to /bin/main.ml and call the function by importing the Hello module, which is attached to the same package as itself:

open Hellolet () = say_hello "Pan"

Run the command dune exec greeter and see if you see a greeting “Hello, Pan!” printed to the terminal! Now you have stepped past the biggest hurdle in Ocaml (in my opinion) and are ready to develop in Ocaml.

--

--

pancy

I’m interested in Web3 and machine learning, and helping ambitious people. I like programming in Ocaml and Rust. I angel invest sometimes.