Programming with OCaml's Local Mode

An example shows how a local list can contain global values, why a non-escaping annotation alone does not ensure stack allocation, and where the experimental feature remains awkward.

Channel
Jane Street
Published
Jun 24, 2024
Video
14:36
Read
3 min
Slides
5
Original
English
Open on YouTube
Scroll to read and the video shrinks to the bottom-right corner. Captions follow your reading language.
Reading languageAvailable in 2 languages
You're reading the original English.
SUMMARY

In 30 seconds

A Jane Street speaker demonstrates local mode in an experimental OCaml compiler branch. Building a temporary list of widgets exposes tail-call restrictions, global modality, wrapper types and the need for a local-allocating map.

  1. 1A list that must not escapeThe intermediate widget list can be passed to get_best only when the function promises not to retain it.
  2. 2Tail-call restrictionThe local argument cannot be used in that tail call, so the example opts out of tail-call optimization.
  3. 3Global elements inside a local listA globally modal field in an unboxed wrapper preserves the elements' global status even when the list is local.
  4. 4Local does not mean stack allocatedOrdinary List.map still allocates on the heap even if its result is treated as local.
  5. 5Allocate in the caller's regionA local-producing map uses exclave in tail position to allocate its result in the caller's region.

Takeaways

  1. A local parameter promises that its argument will not escape, which may require avoiding a tail call.
  2. A local list can contain global elements using a wrapper field with global modality.
  3. An unboxed single-field wrapper can express this distinction without adding a runtime wrapper allocation.
  4. A local annotation expresses non-escape; it does not by itself ensure that a value is allocated on the stack.
  5. A local-producing map can allocate list cells in the caller's region using exclave.
  6. Mode polymorphism and more convenient modality syntax were future possibilities, not completed features in this demonstration.
FULL TEXT

Read the edited text by chapter

Transcript only
01

A temporary widget list

The speaker introduces the example and tries to make its intermediate list local. A local parameter and a nontail call resolve initial escape errors.

In this final installment of the current series on locals, I want to work through a small programming example using the experimental feature in Jane Street's OCaml compiler branch. The example is deliberately contrived, but the errors we encounter illustrate practical issues that can arise when working with local values.

Imagine a map of widgets indexed by integers. We want the best widget associated with a prime below 100. A list of primes is already available, and a get_best function takes a widget list and returns the best element. For the demonstration, I supply placeholder helpers that compile but are not intended to run.

The initial implementation maps over the primes, looks up each widget, collects the results in a list, and passes that list to get_best. It compiles. But the intermediate list is only needed during this function, so I would prefer not to allocate it on the heap.

The temporary widget list
SLIDE 1 / 5The temporary widget listThe initial function maps prime keys to widgets and passes the list to get_best.

Annotating the list as local produces an escape error: the original get_best signature expects a global list and makes no promise that it will not store the argument. Marking its list parameter local gives the compiler the needed non-escape promise.

That exposes another error: get_best is in tail position, so the local argument cannot be passed in that tail call. Adding a nontail annotation keeps the relevant region alive. In this nonrecursive example, sacrificing tail-call optimization is acceptable.

A local argument in a tail call
SLIDE 2 / 5A local argument in a tail callThe compiler reports that the local value escapes because the call is in tail position.
02

Global elements and local structure

Returning an element from a local list reveals a locality problem. A globally modal field in an unboxed wrapper preserves global elements.

The placeholder signature now accepts a local list and returns a global widget, but we have not implemented it. A real implementation that takes the first element reveals the next problem: a value extracted from a local list is treated as local, so returning it globally would let it escape.

After I mark get_best's input local, its inferred result is also local. The caller wants to return a global widget from the original table, however. The distinction we need is a list whose cells are local but whose elements are global.

The solution demonstrated is a wrapper type with a global modality on its single field. The wrapper is marked unboxed, a general OCaml feature for this kind of single-field record: the wrapped value and wrapper have the same runtime representation, so the wrapper itself does not require an extra allocation.

The global modality says that the field remains global even when the enclosing wrapper is local. A local list of these wrappers can therefore contain global elements. Pattern matching extracts the field, allowing get_best to return it globally without letting the local list itself escape.

A globally modal wrapper
SLIDE 3 / 5A globally modal wrapperAn unboxed single-field record lets a local list hold values known to be global.
03

Non-escape versus allocation

Wrapping values restores the desired interface, but a local annotation alone does not change how List.map allocates. A local-producing map uses exclave.

The inferred input type initially expects a map of wrapped widgets, which is not what we started with. Wrapping each widget after the lookup fixes the interface: the input remains a map of ordinary widgets, the result is global, and the intermediate list is local.

Wrapping the widget lookup
SLIDE 4 / 5Wrapping the widget lookupThe map wraps each retrieved widget so the public input remains a map of ordinary widgets.

There is still a subtlety. A local annotation says the list does not escape; it does not by itself guarantee stack allocation. Ordinary List.map allocates its result on the heap, and a global value can be treated as local. The code may type-check while failing to achieve the desired allocation behavior.

To actually allocate the intermediate list locally, I write a separate recursive map. In a full Jane Street environment, library functions can provide this functionality, but the example uses the standard library context and defines its own version.

The map's type must say its result is local. Its cons cells need to be allocated in the caller's region, so the implementation uses exclave in a tail position. With that change, the map returns a locally allocated list and the caller can infer its locality without an explicit annotation.

A local-producing map
SLIDE 5 / 5A local-producing mapThe custom map uses exclave to construct its result in the caller’s region.
04

Practical rough edges

Wrapping elements and maintaining a separate local map introduce verbosity. The speaker discusses possible future improvements while stressing that the feature is experimental.

The example exposes two sharp corners: preserving global elements in a local list requires wrapping and unpacking them, and ordinary map needs a local-producing counterpart to control allocation. The team hopes mode polymorphism may improve this and is exploring possible syntax for inline modalities, but the work remains experimental.

More from this channel

1:48:29EN
JSJane Street· Oct 16, 2026How AI Is Changing Programming: John Krazit and Ron MinskyRon Minsky, John Krazit · Jane StreetJane Street’s AI tooling lead and the Signals and Threads host examine agent infrastructure, security, code review and the changing experience of writing software.Coding agentsDeveloper productivityEvals+2
1:36:37EN
JSJane Street· Sep 23, 2026Aaron Bauer on Teaching Software Engineering in the Age of AIRon Minsky, Aaron Bauer · Jane StreetHow learning goals, active practice and editor design shape engineering education when models can generate the code.AI in EducationSoftware engineeringEducation+2
9 slides1:05:45EN
JSJane Street· Sep 11, 2026Cyrus Omar on Live Programming and a Computational Commons for Humans and AICyrus OmarA demonstration of Hazel shows how immediate feedback, structured collaboration and carefully scoped permissions could support people and AI agents working together.Programming LanguagesHuman-AI CollaborationDeveloper workflows & testing+2
1:25:00EN
JSJane Street· Sep 2, 2026Eric Mannes on Turning Messy External Data Into Trading ResearchRon, Eric Mannes · Jane StreetFrom commodity futures and an Excel risk system to point-in-time datasets and AI, Eric Mannes explains the domain knowledge behind data engineering at Jane Street.Data quality & curationDeveloper workflows & testingInvesting+2
7 slides1:16:29EN
JSJane Street· Aug 26, 2026Grant Sanderson and Alok Puranik: Positional Encodings Through Group TheoryGrant Sanderson, Alok Puranik · 3Blue1BrownStarting with assumptions about attention, they derive matrix exponentials and connect rotations, decay and Jordan blocks to RoPE and ALiBi.Transformers & attentionMathematicsML fundamentals+1
27:39EN
JSJane Street· Aug 21, 20263Blue1Brown and Jane Street on Trading, Machine Learning and CultureGrant Sanderson, Nitya, In Young, Craig, Alok · 3Blue1BrownGrant Sanderson talks with Jane Street colleagues about market making, shared compute, research and the paths that brought them to the firm.Machine LearningFintechCompany Culture+2