OCaml Weekly News

Previous Week Up Next Week

Hello

Here is the latest OCaml Weekly News, for the week of November 18 to 25, 2025.

Table of Contents

Tapak: Experimental Web Framework Based on EIO

Syaiful Bahri announced

Hello! I am a newcomer to the OCaml community, and this is my first post.

I would like to share an experimental web framework I have been working on, based on Eio. I am a happy user of Dream and Opium, but I felt there was still room for improvement.

I’ve shared my motivation here: https://www.sbahri.com/projects/tapak/

I would love to hear your suggestions and opinions regarding the API. What important features do you feel are missing in existing libraries? Personally, I miss the type-safe routing that Servant offers, as well as the Phoenix real-time API.

FUN OCaml is live on YouTube and Twitch

Continuing this thread, Sabine Schmaltz announced

After hiding the original live stream recording, and starting to publish the talk recordings individually on the YouTube channel at https://www.youtube.com/@FUNOCaml, we're almost done listing all the talks!

https://youtu.be/0Hwd7NxQ8_c?si=PkrJtJXPYS1kSAyg

Native debugging on macOS (DWARF support)

Joel Reymont announced

With DWARF v5 support, the compiler now includes proper debug info — line mappings, symbols, and variable names — so LLDB can actually follow your code.

A small LLDB Python plug-in reads OCaml values at runtime and prints them in a readable form: lists, tuples, closures, strings, etc. It follows DWARF location lists to track where each variable lives and uses the runtime headers to decode them. The p and ocaml_vars commands work like normal LLDB commands but understand OCaml values.

It’s not complete yet (records and variants still show as tuples), but it makes debugging native OCaml code straightforward. You can finally set breakpoints by source, inspect locals, and understand what’s in memory without switching to disassembly.

https://joel.id/native-binary-debugging-for-ocaml/

Package Hygiene in Alice

Steve Sherratt announced

https://www.alicecaml.org/blog/package-hygiene-in-alice/

Alice is an OCaml build system and package manager I'm developing in my spare time. This post is about Alice's packaging protocol and how it enforces hygiene while building packages.

crypt 2.0 - unix crypt function

Continuing this thread, Mikhail announced

Minor update: crypt 2.1.

We have added a mutex for the POSIX crypt function and a platform-dependent implementation of crypt, i.e., crypt_r for Linux and FreeBSD. We have also added the Ffi module for accessing native platform bindings.

opam 2.5.0~rc1 and opam-publish 2.7.1

Kate announced

Hi everyone,

We are happy to announce the first release candidate of opam 2.5.0, which boasts a grand total of zero (0) changes compared to 2.5.0~beta1!

We also take this opportunity to announce the release of opam-publish 2.7.1, whose full release notes can be seen here.

Changes in opam-publish 2.7.1

In 2.7.0, opam-publish changed the way user's branches are pushed to their GitHub forks before opening a PR, switching from using SSH keys to using the GitHub API token that opam-publish already requires.

2.7.1 fixes a couple of bugs related to that where opam-publish stopped working if the GitHub Action workflow files of upstream opam-repository are changed, owing to the way GitHub token permissions work. Thanks to @filipeom both for the original contribution in 2.7.0 and for subsequent work on it in 2.7.1.

Read our blog post for more details.

Please report any issues to the opam bug-tracker or the opam-publish bug-tracker.

Try the new opam 2.5.0 release candidate:

The upgrade instructions are unchanged:

For Unix systems

bash -c "sh <(curl -fsSL https://opam.ocaml.org/install.sh) --version 2.5.0~rc1"

or from PowerShell for Windows systems

Invoke-Expression "& { $(Invoke-RestMethod https://opam.ocaml.org/install.ps1) } -Version 2.5.0~rc1"

Please report any issues to the bug-tracker.

Happy hacking, <> <> The opam team <> <> :camel:

ocaml-protoc 4.0, pbrt 4.0

Simon Cruanes announced

Dear caml riders, I'm happy to announce the release of ocaml-protoc 4.0, alongside its runtime libraries pbrt, pbrt_yojson, and pbrt_services.

Ocaml-protoc is a pure OCaml implementation of a protobuf compiler and runtime. Protobuf is a binary serialization format and IDL (interface description language) first introduced by Google in the early 00s, and still in pervasive use there and elsewhere. It is faster (to encode/decode) and more compact than JSON, and is designed for backward compatibility on the wire.

Anyway, this new major release is breaking because it finally follows the standard's semantics about field presence. By default, many fields in protobuf are somewhat optional[^1] and are not serialized at all if not present. Ocaml-protoc now tracks this for every non-required field, even scalars, moving towards better compliance with the standard. It does that either via option or via a presence bitfield. Because of this, the generated code has changed significantly and looks more like what the official protoc produces.

In a nutshell, each protobuf message now becomes a private record type with mutable fields and a presence bitfield. All modification and creation for a type foo is done via make_foo and foo_set_<field> functions; presence can be checked with foo_has_<field>. This means the runtime knows which fields have been explicitly modified.

I don't think ocaml-protoc is 100% compliant with the fine print on default values in proto3, etc. but this is a lot closer than it used to be. Thanks to the work of @lupus there's also a new option validation layer.

detailed example

Let's look at this simple example:

syntax = "proto3";

message Person {
  string name = 1;
  sint64 age = 2;
}

message Store {
  string address = 1;
  repeated Person employees = 2;
  repeated Person clients = 3;
}

message Company {
  string name = 1;
  repeated Store stores = 2;
  repeated Company subsidiaries = 3;
}
(* generated from "orgchart.proto", do not edit *)

(** {2 Types} *)

type person = {
  name : string;
  age : int64;
}

type store = {
  address : string;
  employees : person list;
  clients : person list;
}

type company = {
  name : string;
  stores : store list;
  subsidiaries : company list;
}


(** {2 Basic values} *)

val default_person : 
  ?name:string ->
  ?age:int64 ->
  unit ->
  person
(** [default_person ()] is the default value for type [person] *)

val default_store : 
  ?address:string ->
  ?employees:person list ->
  ?clients:person list ->
  unit ->
  store
(** [default_store ()] is the default value for type [store] *)

val default_company : 
  ?name:string ->
  ?stores:store list ->
  ?subsidiaries:company list ->
  unit ->
  company
(** [default_company ()] is the default value for type [company] *)


(** {2 Formatters} *)

val pp_person : Format.formatter -> person -> unit 
(** [pp_person v] formats v *)

val pp_store : Format.formatter -> store -> unit 
(** [pp_store v] formats v *)

val pp_company : Format.formatter -> company -> unit 
(** [pp_company v] formats v *)


(** {2 Protobuf Encoding} *)

val encode_pb_person : person -> Pbrt.Encoder.t -> unit
(** [encode_pb_person v encoder] encodes [v] with the given [encoder] *)

val encode_pb_store : store -> Pbrt.Encoder.t -> unit
(** [encode_pb_store v encoder] encodes [v] with the given [encoder] *)

val encode_pb_company : company -> Pbrt.Encoder.t -> unit
(** [encode_pb_company v encoder] encodes [v] with the given [encoder] *)


(** {2 Protobuf Decoding} *)

val decode_pb_person : Pbrt.Decoder.t -> person
(** [decode_pb_person decoder] decodes a [person] binary value from [decoder] *)

val decode_pb_store : Pbrt.Decoder.t -> store
(** [decode_pb_store decoder] decodes a [store] binary value from [decoder] *)

val decode_pb_company : Pbrt.Decoder.t -> company
(** [decode_pb_company decoder] decodes a [company] binary value from [decoder] *)
```

new code:

```ocaml

(** Code for orgchart.proto *)

(* generated from "orgchart.proto", do not edit *)



(** {2 Types} *)

type person = private {
  mutable _presence: Pbrt.Bitfield.t; (** presence for 2 fields *)
  mutable name : string;
  mutable age : int64;
}

type store = private {
  mutable _presence: Pbrt.Bitfield.t; (** presence for 1 fields *)
  mutable address : string;
  mutable employees : person list;
  mutable clients : person list;
}

type company = private {
  mutable _presence: Pbrt.Bitfield.t; (** presence for 1 fields *)
  mutable name : string;
  mutable stores : store list;
  mutable subsidiaries : company list;
}


(** {2 Basic values} *)

val default_person : unit -> person 
(** [default_person ()] is a new empty value for type [person] *)

val default_store : unit -> store 
(** [default_store ()] is a new empty value for type [store] *)

val default_company : unit -> company 
(** [default_company ()] is a new empty value for type [company] *)


(** {2 Make functions} *)

val make_person : 
  ?name:string ->
  ?age:int64 ->
  unit ->
  person
(** [make_person … ()] is a builder for type [person] *)

val copy_person : person -> person

val person_has_name : person -> bool
  (** presence of field "name" in [person] *)

val person_set_name : person -> string -> unit
  (** set field name in person *)

val person_has_age : person -> bool
  (** presence of field "age" in [person] *)

val person_set_age : person -> int64 -> unit
  (** set field age in person *)

val make_store : 
  ?address:string ->
  ?employees:person list ->
  ?clients:person list ->
  unit ->
  store
(** [make_store … ()] is a builder for type [store] *)

val copy_store : store -> store

val store_has_address : store -> bool
  (** presence of field "address" in [store] *)

val store_set_address : store -> string -> unit
  (** set field address in store *)

val store_set_employees : store -> person list -> unit
  (** set field employees in store *)

val store_set_clients : store -> person list -> unit
  (** set field clients in store *)

val make_company : 
  ?name:string ->
  ?stores:store list ->
  ?subsidiaries:company list ->
  unit ->
  company
(** [make_company … ()] is a builder for type [company] *)

val copy_company : company -> company

val company_has_name : company -> bool
  (** presence of field "name" in [company] *)

val company_set_name : company -> string -> unit
  (** set field name in company *)

val company_set_stores : company -> store list -> unit
  (** set field stores in company *)

val company_set_subsidiaries : company -> company list -> unit
  (** set field subsidiaries in company *)


(** {2 Formatters} *)

val pp_person : Format.formatter -> person -> unit 
(** [pp_person v] formats v *)

val pp_store : Format.formatter -> store -> unit 
(** [pp_store v] formats v *)

val pp_company : Format.formatter -> company -> unit 
(** [pp_company v] formats v *)


(** {2 Protobuf Encoding} *)

val encode_pb_person : person -> Pbrt.Encoder.t -> unit
(** [encode_pb_person v encoder] encodes [v] with the given [encoder] *)

val encode_pb_store : store -> Pbrt.Encoder.t -> unit
(** [encode_pb_store v encoder] encodes [v] with the given [encoder] *)

val encode_pb_company : company -> Pbrt.Encoder.t -> unit
(** [encode_pb_company v encoder] encodes [v] with the given [encoder] *)


(** {2 Protobuf Decoding} *)

val decode_pb_person : Pbrt.Decoder.t -> person
(** [decode_pb_person decoder] decodes a [person] binary value from [decoder] *)

val decode_pb_store : Pbrt.Decoder.t -> store
(** [decode_pb_store decoder] decodes a [store] binary value from [decoder] *)

val decode_pb_company : Pbrt.Decoder.t -> company
(** [decode_pb_company decoder] decodes a [company] binary value from [decoder] *)

[^1]: the precise semantics of presence are, imho, quite messy.

Cmdliner 2.1.0 – The powershell edition

Brian Ward announced

Hello,

It's my pleasure to announce the version 2.1.0 of cmdliner

Cmdliner is an ISC-licensed library that allows the declarative and compositional definition of command lines with excellent support for command line interface user conventions and standards.

This releases consolidates additional work on command line completion. I added:

I'd like to thank @dbuenzli for his gracious feedback on these changes, and importantly for telling me “no” to several others beforehand :)

For all the other details see the release notes.

Other OCaml News

Old CWN

If you happen to miss a CWN, you can send me a message and I'll mail it to you, or go take a look at the archive or the RSS feed of the archives.

If you also wish to receive it every week by mail, you may subscribe to the caml-list.