Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

OCaml  Module 13 – Runtime, performance and interoperability

Interfacing with C

How OCaml calls C: external declarations, C stubs that read and allocate OCaml values under the garbage collector, dune foreign_stubs, and ctypes.

  • Advanced
  • 15 minutes
  • Examples run with ocaml 5.5.1, opam 2.6.0, dune 3.24.2 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 5 of 15 minutes free
Free: An external declaration names a C function · A stub reads OCaml values and returns one · Practise: break the stub on purpose · Interview questions. With a pass: 3 in-depth parts and 3 interview answers, about 10 minutes.

What you will learn

  • Declare external functions and write C stubs with CAMLparam, CAMLlocal and CAMLreturn
  • Follow the garbage-collector rules for allocating OCaml values inside a stub
  • Build stubs with dune's foreign_stubs, raise OCaml exceptions from C, and avoid boxing
  • Compare hand-written stubs with ctypes, and read an OCaml 5.5 external type

Before you start

Sometimes the routine you need is already written in C: a checksum, a numeric kernel, a system call, or a whole library. OCaml reaches it through a foreign function interface: A way for code in one language to call functions written in another. In OCaml, you declare a C function with external and link a small C stub that bridges the calling conventions.: you declare the function with external, write a small C stub: A small C function that connects OCaml to a C routine: it reads its OCaml arguments with the runtime's macros, does the work in C, and returns an OCaml value, cooperating with the garbage collector when it allocates. that connects OCaml’s calling conventions to C’s, and let the build compile and link it. The reward is a C function that looks like any other OCaml function; the price is that the stub steps outside the type checker and the garbage collector, so a mistake there is a memory bug, not a type error. This lesson writes three stubs that run, and draws the rules that keep them safe.

An external declaration names a C function

An external declaration binds an OCaml name to a C function. It gives the OCaml type and, as a string, the C function’s name:

A first stub: the Adler-32 checksum

adler32/main.ml

(* The external declaration links the OCaml name adler32 to the C
   function tv_adler32. From here on adler32 is an ordinary function
   of type string -> int. *)
external adler32 : string -> int = "tv_adler32"

let () =
  let show s = Printf.printf "adler32 %-11S = 0x%08x\n" s (adler32 s) in
  show "Wikipedia";
  show "";
  show "a";
  show "abc"

adler32/adler32_stub.c

/* A C stub for OCaml: the Adler-32 checksum of a string.
   It reads the OCaml string and returns a plain OCaml int, so it
   allocates nothing on the OCaml heap and needs no GC registration. */
#include <caml/mlvalues.h>

CAMLprim value tv_adler32(value s)
{
  const unsigned char *p = (const unsigned char *) String_val(s);
  mlsize_t len = caml_string_length(s);
  unsigned long a = 1, b = 0;
  for (mlsize_t i = 0; i < len; i++) {
    a = (a + p[i]) % 65521;
    b = (b + a) % 65521;
  }
  return Val_long((b << 16) | a);
}

adler32/dune

(executable
 (name main)
 (foreign_stubs
  (language c)
  (names adler32_stub)))

From the call’s point of view, adler32 is an ordinary function of type string -> int. The compiler emits a call to tv_adler32; it never sees the C body, so the declared type is a promise it trusts rather than checks. The stub reads the string with String_val and caml_string_length, runs an ordinary C loop, and returns the result tagged as an OCaml int with Val_long. It allocates nothing on the OCaml heap, so the garbage collector cannot interfere. Step through one call as it crosses into C and back:

Interactive · message by message One call crossing into the C stub Step through one call of adler32 as it crosses into C, reads the OCaml string, works in plain C and returns.
One call crossing into the C stub, step 4 of 4. Messages so far: OCaml to C stub: adler32 "Wikipedia"; C stub to OCaml: Val_long 0x11e60398.OCamlmain.mlC stubsums a and badler32 "Wikipedia"Val_long 0x11e60398

The drawing shows the whole scene; its 4 steps are listed below.

Every step as text
  1. The call crosses over. main.ml calls adler32 "Wikipedia". Because adler32 was declared external, the compiled call goes to the C function tv_adler32, with the OCaml string handed over as the one argument.
  2. Read the OCaml string. The stub cannot touch the bytes directly. String_val gives a pointer to them and caml_string_length gives the length, so C can read the nine characters of the string.
  3. Work in plain C. With the bytes in hand the stub runs the Adler-32 loop over two running sums in ordinary C. It allocates nothing on the OCaml heap, so the garbage collector cannot interfere here.
  4. Return an OCaml int. The result is a small integer, so Val_long turns the C number into an OCaml int and the stub returns it. Back in OCaml, adler32 "Wikipedia" is 0x11e60398.

Build it with dune and run it: the checksum of "Wikipedia" is 0x11e60398, and the empty string gives 0x00000001, because RFC 1950, which defines Adler-32, starts its two sums at 1 and 0.

Build the stub with dune and run it Python · adler32_session.py
from session import files, run

# adler32/: a dune executable whose only C file, adler32_stub.c, holds one
# stub, tv_adler32. main.ml declares `external adler32 : string -> int =
# "tv_adler32"` and prints the checksum of four strings. (foreign_stubs
# (language c) (names adler32_stub)) in the dune file compiles and links the
# C file. The checksum of "Wikipedia" is the value printed in the Adler-32
# article, 0x11e60398.
files(
    "adler32/dune-project=dune-project",
    "adler32/dune=dune",
    "adler32/adler32_stub.c=adler32_stub.c",
    "adler32/main.ml=main.ml",
)
run("""
ls
dune build 2>&1
dune exec ./main.exe
""")

Output

$ ls
adler32_stub.c
dune
dune-project
main.ml
$ dune build 2>&1
$ dune exec ./main.exe
adler32 "Wikipedia" = 0x11e60398
adler32 ""          = 0x00000001
adler32 "a"         = 0x00620062
adler32 "abc"       = 0x024d0127

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 adler32_session.py

Number Base Converter Turn 0x11e60398 into decimal, or a checksum of your own back into hex, to read the two 16-bit halves.

Key idea

An external declaration is a promise, not a check: the compiler trusts the OCaml type you give and calls the named C function directly, so the type must match the C function exactly.

Checkpoint

Check: Declaring an external function

  1. One question An external declaration gives an OCaml type and the name of a C function. What does the compiler do with that type?

    Choose one answer.

    Show the answer: One question

    Answer: It trusts the type: it cannot check it against the C code, so a wrong type is a memory-safety bug

    An external declaration is a promise the compiler trusts: it emits calls that assume the given type and never checks it against the C side, so declaring the wrong type is an unchecked, memory-unsafe bug.

A stub reads OCaml values and returns one

Every argument a stub receives is an OCaml value: one machine word that is either an immediate (an int, a char, a constant constructor, with its low bit set to 1) or a pointer to a heap block (a string, an array, a record, a tuple). A stub never touches that word directly; it uses the runtime’s macros, which the manual’s chapter on interfacing C with OCaml defines. The common ones:

To do this Use Notes
Read an int argument Long_val(v) the reverse of Val_long
Return an int Val_long(n) tags a C integer as an immediate
Read a string’s bytes String_val(v) a pointer into the heap block
Its length caml_string_length(v) counts null bytes too, unlike strlen
Read a float from a float array Double_field(v, i) the array is unboxed
Return a boxed float caml_copy_double(x) allocates on the heap

The Adler-32 stub used only the first rows: it read a string and returned an immediate. Because it allocated nothing and held no OCaml value across an allocation, it needed no further ceremony. The moment a stub allocates, a second set of rules begins, and that is where most stub bugs live.

A value is not a C pointer

Reading a string argument with strlen or casting it to char * and keeping it is wrong twice over: an OCaml string may contain null bytes, so strlen gives the wrong length, and the garbage collector may move the block, so a saved C pointer can dangle. Use String_val and caml_string_length each time you need them.

Checkpoint

Check: Reading and returning OCaml values

  1. One question Inside a stub, how do you reach the bytes of an OCaml string argument s?

    Choose one answer.

    Show the answer: One question

    Answer: String_val(s) for a pointer to the bytes and caml_string_length(s) for the length

    A stub cannot touch OCaml data directly: String_val gives the bytes of a string and caml_string_length gives its length, which strlen cannot because an OCaml string may hold null bytes.

Practise: break the stub on purpose

Work with the adler32/ example, and predict each result before you build:

  1. Add show "a\000b" to main.ml, a three-byte string whose middle byte is null. The checksum of those bytes is 0x018800c4. Then replace caml_string_length(s) in the stub by strlen((const char *) String_val(s)) and predict the new value: strlen stops at the null byte, so only the byte a is summed, and the checksum of "a" is 0x00620062.
  2. Change the OCaml type of adler32 to bytes -> int and say whether dune build fails, and in which file. The compiler checks every use of adler32 in main.ml against the declared type, so the string passed by show is refused there, while the C file is never consulted.

Then test yourself on the declaration and on reading values:

Practice that adapts to you

Practise this lesson

4 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 4 In external adler32 : string -> int = "tv_adler32", what does the string "tv_adler32" name?

    Choose one answer.

    Show the answer: Question 1 of 4

    Answer: The C function (the primitive) that OCaml calls for adler32

    The string after the equals sign is the C function's name, the primitive that OCaml calls; everything to the left is the OCaml type the compiler trusts.

  2. Question 2 of 4 A stub takes a C double but its external is declared int -> int. The program builds and links. What is the most likely outcome?

    Choose one answer.

    Show the answer: Question 2 of 4

    Answer: Undefined behaviour: the bit pattern of an OCaml int is handed to C as a double, with no error

    An external's type is unchecked against C. A wrong type sends the wrong representation across the boundary with no diagnostic, which is why the declaration must match the C function exactly.

  3. Question 3 of 4 A stub has computed a small C integer result. How does it return it as an OCaml int?

    Choose one answer.

    Show the answer: Question 3 of 4

    Answer: Val_long(result), which tags the C integer as an OCaml immediate int

    An OCaml int is an immediate, tagged value. A stub returns one by tagging its C integer with Val_long; no heap allocation is involved, which is why such a stub needs no GC registration.

  4. Question 4 of 4 Why does the Adler-32 stub use caml_string_length(s) and not strlen on the pointer from String_val(s)?

    Choose one answer.

    Show the answer: Question 4 of 4

    Answer: An OCaml string may contain null bytes, and strlen stops at the first one

    An OCaml string carries its length in the block, so caml_string_length gives the true number of bytes. A C string ends at the first null byte, so strlen would sum the three bytes of "a", null, "b" as just the first one.

Interview questions

These questions come up when a team adds its first C binding. Work out your own answer, then open the model answer to compare the reasoning and the fix.

  1. Mid to senior

    A hand-written C stub returns a record built with caml_alloc. Under load it sometimes returns garbage or crashes, but never in small tests. What rule is most likely broken, and how would you find and fix it?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A colleague marks a numeric stub [@@noalloc] to speed it up, but the stub calls caml_copy_double to return a float. The program passes every test, then crashes in production. Explain the connection.

    Model answer, Pro. First step and follow-ups

  3. Mid to staff

    Your team must bind a C library of about forty functions, mostly taking ints, doubles and pointers, to OCaml. Would you write hand stubs or use ctypes, and what decides it?

    Model answer, Pro. First step and follow-ups

The garbage-collector rules for a stub that allocates, and how to raise an OCaml exception from C Pro: opens with a Pro, Premium or Ultimate pass

A stub that allocates on the OCaml heap has to cooperate with the garbage collector, because allocation is exactly what can start a collection, and a minor collection moves the young values the stub is still holding. The rules are few and mechanical, and once followed they make an allocating stub as safe as a pure-OCaml function.

In this part:

  • Registering values, and raising from a stub

1 diagram · 2 runnable examples (Python, main.ml, min_max_stub.c) · about 6 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Building stubs with dune's foreign_stubs, and the attributes that avoid boxing on numeric calls Pro: opens with a Pro, Premium or Ultimate pass

Writing the stub is half the job; the build has to compile the C file with the OCaml runtime’s headers on its include path and link it into the executable or library. dune does this with one stanza, and OCaml has three attributes that make a numeric stub avoid boxing its arguments and its result.

In this part:

  • Building and linking stubs with dune

2 runnable examples (Python, dune, main.ml, popcount_stub.c) · about 4 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Binding C without writing stubs: ctypes, and reading an OCaml 5.5 external type Pro: opens with a Pro, Premium or Ultimate pass

Hand-written stubs are not the only way to reach C. The ctypes library describes a C function’s signature in OCaml and calls it with no C file at all, which changes the trade-off for larger bindings. OCaml 5.5 also added a small type-system feature, external types, that names the foreign-defined types such a binding works with.

In this part:

  • Binding C without writing the stubs

about 7 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Flashcards · 4 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. How does OCaml call a routine that is already written in C?

    Through its foreign function interface: declare the function with external, write a small C stub that connects OCaml's calling conventions to C's, and let the build compile and link it.

  2. What does the compiler check about an external declaration's type?

    Nothing: it trusts the type you give and calls the C function directly, so a wrong type is a memory bug, not a type error.

  3. How does a C stub read its arguments and return an integer?

    Its arguments are OCaml values read through macros such as String_val and Long_val, and it returns an immediate with Val_long.

  4. Why must a stub not read an OCaml string with strlen, or keep its char *?

    An OCaml string may contain null bytes, so strlen can give the wrong length, and the collector may move the block, so a saved pointer can dangle. Use String_val and caml_string_length each time.

Key takeaways

  • An external binds an OCaml name to a C function and gives a type the compiler trusts without checking, so a wrong type is a memory bug, not a type error.
  • A stub reads its arguments as OCaml values through macros like String_val and Long_val and returns an immediate with Val_long; a stub that only reads and returns needs no further ceremony.
  • A string argument is read with String_val and caml_string_length, never with strlen, because an OCaml string may contain null bytes and the block may move.

Terms in this lesson

C stub
A small C function that connects OCaml to a C routine: it reads its OCaml arguments with the runtime's macros, does the work in C, and returns an OCaml value, cooperating with the garbage collector when it allocates.
foreign function interface
A way for code in one language to call functions written in another. In OCaml, you declare a C function with external and link a small C stub that bridges the calling conventions.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.