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.
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:
│ ├── dune
│ └── main.ml
│ └── dune
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!"
/bin contains two files —
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
(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
(synopsis "A short synopsis")
(description "A longer description")
(depends ocaml dune)
(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.
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
The more interesting part is the
dune file in 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
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"
(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:
(public_name simple.hello)) ;; Required to define in the simple namespace
Add a file name
/lib/hello.ml with a single function
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.