Understanding how to import a dune module

I’m trying to understand the dune build system and how to ‘import’ modules into OCaml files.

The way I understood, initially, was that in a dune file like this:

(library
 (name tcpip_stack_direct)
 (public_name tcpip.stack-direct)
 (libraries logs ipaddr lwt result fmt mirage-time mirage-random
   mirage-protocols mirage-stack mirage-net ethernet))

Then if I need to use this I’d do

Tcpip_stack_direct.Filename1.Make(...)

where filename1.ml is a file in the same folder as that dune project file, and that contains a Make.

However if we go into the dune file of the ipv4 part of the code:

(library
 (name tcpip_ipv4)
 (public_name tcpip.ipv4)
 (libraries logs mirage-protocols ipaddr cstruct rresult tcpip
   tcpip.udp mirage-random mirage-clock randomconv lru)
 (preprocess (pps ppx_cstruct))
 (wrapped false))

In order to use it, I should do

Tcpip_ipv4.Wire.Make(...)

(as example)

However, this code uses it like this:

module I = Ipv4.Make(E)(Clock)(OS.Time)

where did Ipv4 come from?

Some of the time, your initial understanding will be correct: a file foo.ml inside a library with name lib will be accessible to your code at Lib.Foo. This is because Dune provides a feature called “wrapping” which keeps all of the files in a single top-level module named after the library. There are two (?) exceptions to this:

  1. If (wrapped false) is specified in the library dune file (as is the case here), this mechanism is disabled. The library may provide more than one top-level module, and they can be named arbitrarily. Sure enough, if we consult the documentation for mirage-tcpip, we see that the library tcpip.ipv4 is exporting a whole bunch of top-level modules. At the time when this code was written, Ipv4 was one of them (it isn’t any more).

  2. If the library contains a file with the same name – i.e. library lib contains a file lib.ml – this module becomes the wrapper module. Only modules in the signature of lib.ml will be accessible outside. This is often used to hide utils.ml files and other things that you don’t want to export to users of your library.

In general: use Dune’s wrapping and (2) if you want to hide top-level files or shrink their interface. As you’ve discovered, the alternatives are confusing for end-users and can lead to namespace collisions between different libraries.

2 Likes