(Sub)module not found in test when using dune

Hi, I’m new to OCaml and I’m trying to understand how to use dune.

I constructed a minimal example that looks like this:

.
├── dune-project
├── lib
│   ├── dune
│   ├── mylib.ml
│   └── mymod.ml
└── test
    ├── dune
    └── test.ml

The dune-project file just contains one line: (lang dune 3.12)

The lib/dune file contains: (library (name mylib))

The test/dune file contains:

(test
 (name test)
 (libraries mylib))

The lib/mylib.ml file contains:

let one = 1
let _ = assert (one = 1)
let _ = assert (Mymod.two = 2)

And the lib/mymod.ml file, which is supposed to be a submodule, contains:

let two = 2

Now my test in test/test.ml looks like this:

let _ = assert (Mylib.one = 1)
let _ = assert (Mylib.Mymod.two = 2)

I get the following error:

$ dune test
File "test/test.ml", line 2, characters 16-31:
2 | let _ = assert (Mylib.Mymod.two = 2)
                    ^^^^^^^^^^^^^^^
Error: Unbound module Mylib.Mymod

Why does Mymod.two work inside lib/mylib.ml but not inside the test. What am I doing wrong?

See Using Modules (15 characters long now) - #2 by yawaramin

From that other thread:

I do have a lib/dune file that says (library (name mylib)), so that would mean the module in lib/mylib.ml would be available as Mylib.Mylib and the module in lib/mymod.ml would be available as Mylib.Mymod. But that isn’t the case, i.e. this fails:

let _ = assert (Mylib.Mylib.one = 1)
let _ = assert (Mylib.Mymod.two = 2)

Instead, if I change (library (name mylib)) to (library (name foo)) (and modify test/dune accordingly), then these are suddenly available as Foo.Mylib and Foo.Mymod, i.e. this works:

let _ = assert (Foo.Mylib.one = 1)
let _ = assert (Foo.Mymod.two = 2)

Why does the first fail and the second work?

If you create a mylib.ml file for a lib named mylib in the dune file, it does not behave as a submodule but rather allows you to define the content of the top-level Mylib module. This allows you to re-export some submodules for instance, with a different name.

So the solution then is to modify mylib.ml, I guess:

+module Mymod = Mymod
 let one = 1
 let _ = assert (one = 1)
 let _ = assert (Mymod.two = 2)

Would that be the idiomatic way to go?

See Using Modules (15 characters long now) - #4 by cuihtlauac

I think so. If you want top-level values (or types) to appear in Mylib, then I think you need an explicit mylib.ml. And if you do that, then you need to define module aliases to export the inner modules.

I tried to find the relevant info in the dune documentation but it seems this is only mentioned in passing at the beginning of the documentation of the library stanza.

Thank you both for your help. Maybe the documentation could be improved. It was really confusing me a lot, but I think it now makes sense (to me).

The documentation can definitely be improved. If you like, you can send a PR and suggest some changes to their repo Welcome to Dune’s Documentation! — Dune documentation

Once I get better accustomed with the ecosystem, I may consider contributing. But I feel like I’m not at that point yet. :sweat_smile:

Hi @jbe

This matter is tentatively addressed in the new (draft) tutorial Libraries With Dune, which I have just announced. As @K_N noticed, this wasn’t very well documented.