How to construct a record which its type was defined in another file?

Suppose we have the following lib/klib.ml :

type kinfo = {
  name: string
}

With lib/klib.mli :

type kinfo

And we have another record with the same record’s element name in the bin/main.ml :

open Core

type cinfo = {
  name: string
}


let () =
let ci = {name = "ok"} in
  print_endline ci.name

let () =
let ki : Klib.kinfo = { name = "hello"} in
  print_endline ki.name

There’s an error when tried to dune build

(executables
  (names main)
  (preprocess (pps ppx_let))
  (libraries core async klib))
 $ dune build
File "bin/main.ml", line 13, characters 22-39:
13 | let ki : Klib.kinfo = { name = "hello"} in
                           ^^^^^^^^^^^^^^^^^
Error: This expression has type cinfo but an expression was expected of type
         Klib.kinfo

The type annotation that you added (let ki : Klib.kinfo = ...) is enough for the compiler to disambiguate and pick up the correct type in this case. The reason it’s not doing so in this case is that lib/klib.mli is exposing the kinfo type as an abstract type so bin/main.ml can’t see its internals and construct an instance of it.

Adding the type definition to lib/klib.mli should solve it. lib/klib.mli should be

type kinfo = {
  name: string
}
1 Like

Thanks, it works in this way.

I was curious that if it is appropriate to export a record type?

In simple this case, we are good to modify both the klib.ml and klib.mli at the same. But in real world, we might have more than 10 fields inside a record, and it might easy prone to bugs in that case.

Hi, you won’t have bugs because the compiler will force the type declarations to match in both files.
But you will break the code of users if you update the record type.
Instead you could expose the type as abstract, and provide constructors and deconstructors, which could be less prone to breaking.

An alternative way to annotating the type is to prefix one of the fields with its module name: let ki = { Klib.name = "hello" }

OCaml finds record field names by the module they’re defined in, so without the prefix it thinks name is referring to Main.name.

2 Likes