Library / SDK
janestreet/base avatar
janestreet/base

janestreet/base, an OCaml standard library that replaces the standard library

Standard library for OCaml

1,090 stars179 forksOCamlMIT

At a glance

What is it?
Base is Jane Street's standard library for OCaml, designed as a wholesale replacement for the compiler's own, with portable modules only, integer comparison by default and lint rules that forbid opening Stdlib. The published release is v0.15.1 from 2022, while the branch has moved on since.
Who is it for?
Adopt Base if you are building an OCaml project where portability and predictable comparison semantics matter more than the standard library's default behaviour, and you can live without the non-portable parts, since I/O is deliberately left to other libraries.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 105 days ago.
What is it written in?
Mainly OCaml, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A replacement, not a supplement

Base is a standard library for OCaml, providing a standard set of general purpose modules that are well-tested, performant and fully portable across any environment that can run OCaml code.

The distinguishing sentence is the next one. Unlike other standard library projects, Base is meant to be used as a wholesale replacement of the standard library distributed with the OCaml compiler. It makes different choices and does not re-export features that are not fully portable, such as I/O, which are left to other libraries.

That is a stronger commitment than a normal library. Most standard library alternatives are drop-in or additive, and you use whichever modules you feel like. Base is designed so that after you open it, the compiler's own library is not what you are using at all, and the parts of the compiler library that cannot be guaranteed portable are simply not there. I/O is the named example, so a project that needs to read files or write to a socket is expected to take that dependency elsewhere.

The audience is therefore production OCaml code where the environment is not known in advance, which in practice means code that has to run on more than one platform, and code written by teams that have already made the mistake of depending on a comparison operator's behaviour in one place and a different one in another.

The project is MIT licensed and published on opam, and the API documentation is published on ocaml.org.

Comparison operators default to integers, and that is the point

The first difference the documentation asks you to read is comparison, and the reason is that the standard library's behaviour is a known footgun.

The comparison operators exposed by the OCaml standard library are polymorphic.

ocaml
val compare : 'a -> 'a -> int
val ( <= ) : 'a -> 'a -> bool

What they implement is structural comparison of the runtime representation of values, and since these are often error-prone, in the sense that they do not correspond to what the user expects, they are not exposed directly by Base.

So the library removes the polymorphic comparison operators from the default environment. To use polymorphic comparison with Base, you use the Poly module, and the default comparison operators Base does expose are the integer ones, in the same way that the default arithmetic operators are the integer ones. That is a deliberately small surface: the operators you get by default do one thing and cannot surprise you.

For arbitrary data structures, the documentation recommends the specific compare functions.

ocaml
List.compare String.compare x y

The ppx_compare rewriter offers an alternative way to write the same thing.

ocaml
[%compare: string list] x y

That is a nice property of the design. A project can decide, per type, what comparison means, and the decision is written down in code that the compiler checks, rather than inherited from a structural comparison that happens to work on the runtime representation of your type. For a library that has to be correct in any environment, replacing one guess with a declared rule is worth a great deal.

The ppx generators are a development tool, not a build dependency

Base uses a few ppx code generators, and the list is short. They implement reliable and customizable comparison of OCaml values, reliable and customizable hash of OCaml values, and conversions between OCaml values and s-expressions.

What makes this worth reading is the sentence that follows. Base does not need these code generators to build. What it does instead is use ppx as a code verification tool during development, in a fashion very similar to expectation tests.

That is an unusual arrangement and it has a practical consequence for anyone who wants to hack on the library. The generated code is checked in, so a contributor without the ppx toolchain can still build the project, and the ppx run happens as a check rather than as a transformation of the source.

The generated blocks are marked in the source, and the convention is visible in the code.

ocaml
type t = ... [@@deriving_inline sexp_of]
let sexp_of_t = ...
[@@@end]

The code between the deriving_inline marker and the closing marker is generated code. The documentation is frank that the generated code is currently quite big and hard to read, and that the project is working on making it look like human-written code. It also offers an Emacs Lisp snippet for hiding those blocks, which is the sort of detail that tells you the problem is real and the workaround is necessary rather than optional.

There is a related limitation stated plainly. Things are not yet set up in the git repository to make it convenient to change types and update the generated code, but they will be set up soon. That sentence, and the deprecated tags in the release list, are the kind of dated detail that a project description written for its own users can carry without noticing.

The coding rules are enforced, and the Stdlib is deliberately awkward

The coding rules section lists rules that are enforced by lint tools, and they are the most opinionated part of the project.

The first is that opening the Stdlib module is not allowed. Inside Base, the OCaml stdlib is shadowed and accessible through the Stdlib module, and opening Stdlib is forbidden so that the project knows exactly where things come from. The second rule follows from the first: Stdlib.Foo modules cannot be aliased, and one must use Stdlib.Foo explicitly, to avoid having to remember a list of aliases at the beginning of each file.

The third rule is the subtle one. For some modules that exist in both the OCaml stdlib and Base, such as String, Base defines a module String0 for common functions that cannot be defined directly in Base.String, because putting them there would create a circular dependency. Except for String itself, other modules are not allowed to use Stdlib.String and must use either String or String0.

Then the formatting rules: indentation is exactly the one of ocp-indent, and a few other coding style rules are enforced by ppx_js_style. The Base specific coding rules are checked by ppx_base_lint, in the lint subfolder of the repository.

The enforcement detail is worth knowing. These checks are currently not run by dune, but the project says it will soon get a dev flag to run them automatically.

That is a small but meaningful admission. A rule that is only checked by a separate command is a rule that new contributors will break, and the project knows it. The rules themselves are still the right call, since the alternative, an OCaml codebase where you cannot tell which comparison operator is in scope, is worse.

The two String functions that catch everyone out

Most types in Base have sexp_of_t and t_of_sexp functions for converting between values of that type and their s-expression representations. That part is conventional.

One pair deserves special attention, and the documentation names it: String.sexp_of_t and String.t_of_sexp. These have the same types as Sexp.of_string and Sexp.to_string but very different behaviour.

The distinction is between two encodings that look the same. String.sexp_of_t and String.t_of_sexp encode and decode strings embedded in a sexp representation. Sexp.of_string and Sexp.to_string encode and decode the textual form of s-expressions.

The example in the documentation shows the two pairs side by side, embedding a string inside a sexp with List.sexp_of_t String.sexp_of_t, and writing the textual form with Sexp.to_string to a file.

ocaml
  open! Base
  open! Stdio

  (* Embed a string in a sexp *)

  let example_sexp : Sexp.t = List.sexp_of_t String.sexp_of_t [ "hello"; "world" ]

  let () =
    assert (Sexp.equal example_sexp (Sexp.List [ Sexp.Atom "hello"; Sexp.Atom "world" ]))
  ;;

Two things to notice. The opening syntax is open! rather than open, which is the operator that makes the shadowing of the standard library visible at the call site, and the assertion compares with Sexp.equal rather than structural equality, which is the comparison discipline from the previous section applied to a value.

Getting these two pairs the wrong way round is the kind of mistake that produces a file containing a quoted string where you expected a raw one, and it is a mistake the type system will not catch, because the types are identical.

Installation, build dependencies, and a Makefile that is three commands

Base is installed through opam.

bash
$ opam install base

The dependency story is unusually clean. Base has no runtime dependencies and is fast to build, and its sole build dependencies are dune, which itself requires nothing more than the compiler, and sexplib0.

That is the direct consequence of the wholesale replacement design. A library that shadows the standard library and refuses to re-export I/O has no use for a logging library, and one that does not depend on unix cannot pretend to be portable.

The Makefile in the repository is correspondingly short. The default target is dune build, install is dune install with an optional prefix argument, uninstall is dune uninstall, reinstall chains the two, and clean is dune clean.

bash
dune build
bash
dune install

The Makefile exists as a convenience layer, not as a build system of its own, which is the standard shape for a dune project.

The rest of the repository follows the same logic. There is an src directory for the library, a ppx directory for the code generators, a lint directory for ppx_base_lint, a generate directory, a shadow-stdlib directory for the mechanism that shadows the compiler's library, a composition_infix directory, an md5 directory, a dune-project file, a base.opam file with the package metadata, and an .ocamlformat file for the formatter configuration. There is a ROADMAP.md and a CHANGES.md at the top level, plus a Makefile, a LICENSE.md, a CONTRIBUTING.md and a README.org, the last of which is where this documentation itself lives, in Org mode with the source blocks embedded in it.

A release from 2022 and a branch that keeps moving

The published versions are v0.15.1 from 2022-09-29, v0.14.3 from 2022-01-25 and v0.14.2 from 2021-11-22. The repository is not archived, and the last push was on 2026-07-10.

So the picture is the familiar one for a Jane Street library: a stable published version, a branch that continues to receive work, and a gap between them that is measured in years rather than months. The CHANGES.md file at the top level is where the release history lives, and the ROADMAP.md is where the plan lives, which is a better arrangement than a changelog embedded in release notes.

That gap matters more than usual for a standard library replacement, because a project that depends on Base is depending on a large surface. Between v0.15.1 and the current head there may be behavioural changes a project has not seen, and the version its opam repository hands it may not be the version the maintainers are working on. A team that wants the current code has to build from source.

The licence is MIT, and there is a LICENSE.md at the top level. The README.org is the whole user documentation, which is a real difference from most libraries: instead of a separate documentation site with a tutorial, a reference and a migration guide, the guidance on the major differences is the first thing you read, in the same file, written in the same voice as the code.

The last line of the coding rules section is the kind of thing a project is candid about and a user should check before assuming a tool is finished: the lint checks are not run by dune yet, and the project says it will get a dev flag to run them automatically.

Editorial conclusion

Adopt Base if you are building an OCaml project where portability and predictable comparison semantics matter more than the standard library's default behaviour, and you can live without the non-portable parts, since I/O is deliberately left to other libraries. Do not adopt it for a small script or for a codebase that leans on the standard library's I/O, and do not expect a recent release, because the last published version is v0.15.1 from 2022-09-29 even though the repository is still being worked on. Before you switch, read the differences section, plan how you will reach the standard library through the Stdlib module, and check the version your opam repository resolves to.

Frequently asked questions

How do I install Base for OCaml?

Through opam, with `opam install base`. Base has no runtime dependencies, and its only build dependencies are dune and sexplib0.

What is the main difference between Base and the OCaml standard library?

Base is meant as a wholesale replacement for the standard library distributed with the OCaml compiler. It does not re-export features that are not fully portable, such as I/O, which are left to other libraries, and the compiler stdlib is shadowed and reached through the Stdlib module.

How do I compare values when using Base?

The polymorphic comparison operators of the OCaml standard library are not exposed by Base. Use the Poly module for polymorphic comparison, or the specific compare functions such as List.compare String.compare, and the ppx_compare rewriter for the generated alternative.

Why does Base forbid opening the Stdlib module?

Inside Base the OCaml stdlib is shadowed and reachable through the Stdlib module, and opening it is forbidden so the project knows exactly where values come from. Stdlib.Foo modules cannot be aliased either, and the rules are checked by ppx_base_lint in the lint subfolder.

What is the current release of Base?

The published versions are v0.15.1 from 2022-09-29, v0.14.3 from 2022-01-25 and v0.14.2 from 2021-11-22, while the last push to the repository was on 2026-07-10. The project is MIT licensed.

Official sources

  1. Issues
  2. janestreet/base on GitHub
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/janestreet-base.svg)](https://hysenlabs.com/projects/janestreet-base)

Was this useful?