# hobbes: a strongly typed expression language with an LLVM JIT behind it

> hobbes is a C++ library and language for evaluating dynamic expressions, compiling them to native code with LLVM, storing structured data and querying it. It is built for embedding in high performance C and C++ applications, and it deliberately has no sandbox and no array bounds checks, which is the single most important thing to understand before adopting it.

**morganstanley/hobbes** — A language and an embedded JIT compiler

- Repository: https://github.com/morganstanley/hobbes
- Website: http://hobbes.readthedocs.io/
- Stars: 1,233 · Forks: 118
- Language: C
- License: Apache-2.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/morganstanley-hobbes

## A language, a compiler and a runtime, all in one library

The project describes itself as a language, embedded compiler, and runtime for efficient dynamic expression evaluation, data storage and analysis, and the repository description is shorter: a language and an embedded JIT compiler.

The feature table in the README is the real map of the project.

Building covers how to build and install. Embedding covers using hobbes in C++ programs. Evaluation covers evaluating basic hobbes expressions. Storage covers recording data for out-of-band analysis. Networking covers interacting with remote hobbes processes. Comprehensions cover transform, sort, join, filter and group operations on sequences for data analysis. Pattern Matching covers efficiently classifying and destructuring data. Parsing covers parsing text with LALR(1) grammars. Type Classes cover overloading and compile-time calculation. Unqualifier Modules cover user-defined compiler plugins for custom constraint handling.

That last group is worth a moment. Type classes give overloading with compile-time resolution, which is how a language that is strongly typed can still have a generic-looking print function or a user-defined sum. Unqualifier modules are the escape hatch where the user writes a plugin that decides how a constraint is discharged, which is the mechanism behind a lot of the language's expressiveness.

The primary language is C, the licence is Apache-2.0, the home page is hobbes.readthedocs.io, and the documentation is in the doc/ directory in the repository.

## The safety note comes before the build instructions

The README puts a note on Hobbes Usage before anything else, and it is the most consequential text in the project.

Hobbes is built for high performance integration with C/C++ applications. While Hobbes is a strongly typed language that offers compile-time checks, it doesn't have a sandboxed runtime environment or runtime safety features. By design, Hobbes gives direct access to memory and does not have array bounds checks. Additionally, Hobbes supports compilation and execution of native code remotely over a network (RPC). This feature is meant for use within your trusted internal network only. If you choose to utilize such functionality, you need to be aware of these design choices and understand the security implications.

Read that carefully. The compile-time type checking is real, and it is what makes a dynamically evaluated expression safe from the type confusion point of view. But an expression that type checks can still index out of bounds, and the direct memory access is the point of the design, not an oversight.

The RPC capability is a second, separate exposure. A hobbes process can compile and execute native code on request over the network, which means the network endpoint is effectively remote code execution by design. The README scopes it to a trusted internal network, and a team that exposes a hobbes process to anything else has created a very large hole.

This is the reason to think of hobbes as a component of a trusted system rather than a tool for processing user input.

## Building against LLVM, cmake and a C++17 compiler

The build requirements are explicit: LLVM 3.3 or later, cmake 3.19 or later, GNU gcc 4.8 or later, and a version 2.5 or later Linux kernel.

With those installed, the build is three commands.

```bash
$ cmake .
$ make
$ make install
```

There is one likely snag, and the README names it. Depending on where you have installed LLVM, this might not work until you give cmake the path to LLVM's cmake modules, specifically the path to LLVM's LLVMConfig.cmake file. If you set the environment variable LLVM_DIR to point to that directory, then the previous steps should complete successfully.

The output matters for how you embed it. The build produces a static library, libhobbes.a, which can be linked into a C++ executable, and if you want to use hobbes in a .so instead, the build produces a different static library to use, libhobbes-pic.a. The distinction is position independent code, which is what makes a shared object work.

Two utility programs come out of the build as well. The hi program is a basic interactive interpreter for hobbes expressions, and the hog program will efficiently record structured data produced by applications into data files, which can be loaded and queried at the same time by hi.

The README points out that the source code for these two programs can be informative as well, because they demonstrate many different aspects of the hobbes API. That is a fair piece of advice for any library with a thin README, and the examples/ directory with a rosetta/ subdirectory is the place to look for worked examples.

## Embedding: a cc object, compileFn, and a memory pool you must reset

The embedding example is a small C++ shell, and it shows the whole API surface you need for a basic integration.

```C++
#include <iostream>
#include <stdexcept>
#include <hobbes/hobbes.H>

int main() {
  hobbes::cc c;

  while (std::cin) {
    std::cout << "> " << std::flush;

    std::string line;
    std::getline(std::cin, line);
    if (line == ":q") break;

    try {
      c.compileFn<void()>("print(" + line + ")")();
    } catch (std::exception& ex) {
      std::cout << "*** " << ex.what();
    }

    std::cout << std::endl;
    hobbes::resetMemoryPool();
  }

  return 0;
}
```

The README then explains each step. To compile any expression you construct a hobbes::cc object. In the context of an exception handler, you compile functions out of it with the compileFn method, giving it the type you expect back, void() in this case, and a string that can compile to a value of that type. If any stage of compilation fails, a parse error or a type mismatch for example, an exception with details about the failure is raised. Finally, you call hobbes::resetMemoryPool() to release memory dynamically allocated by the compiled expression.

The memory model is the part that needs care. A compiled function that allocates memory does so out of a memory region, a dynamically-growable sequence of bytes where allocations come from, and it is deallocated in bulk when resetMemoryPool() is called. That makes allocation and deallocation very efficient, but requires some thought to establish logical transaction boundaries. The active memory region is thread-local, so you can use the same function pointer in several threads concurrently without worrying about synchronization issues.

Compiled functions themselves are a different lifetime. They are released only when the hobbes::cc object is destroyed, or when hobbes::cc::releaseMachineCode is used to destroy them.

## Linking a hobbes program by hand

The README gives the exact compile line for the example shell, and it is instructive because it shows the LLVM surface you are signing up for.

```bash
$ g++ -pthread -std=c++17 -I <path-to-hobbes-headers> -I <path-to-llvm-headers> test.cpp -o test -L <path-to-hobbes-libs> -lhobbes -ldl -lrt -ltinfo -lz -L <path-to-llvm-libs> `llvm-config --libs x86asmparser x86codegen x86 mcjit passes`
```

The important detail is the backtick invocation of llvm-config. The inline invocation is typical with users of LLVM, to avoid explicitly listing several libraries, and the README notes that the explicit path statements may not be necessary depending on where and how LLVM and hobbes have been installed on your system.

Two things follow from that line. First, the toolchain is x86 specific, since the LLVM components requested are x86asmparser, x86codegen, x86, mcjit and passes. The JIT is a machine code JIT via the MCJIT component, which is what makes the compiled expressions run at native speed. Second, the dependency set is not small: libdl, librt, libtinfo and zlib come along with it, which is normal for an LLVM-linked program but worth knowing before you embed it in something with a strict dependency budget.

The C++17 requirement is also notable, since the stated minimum compiler is GNU gcc 4.8, which predates C++17. The gcc figure is the floor for the compiler being able to build hobbes itself, while your own embedding code needs a compiler that supports the standard you compile against.

## What the language actually looks like

The evaluation section starts with the simplest possible expressions, and the point of the table is the type system underneath.

```
> 'c'
'c'
> 42
42
> 3.14159
3.14159
```

A character literal, an integer and a float, each echoing its own type back at you. The README says that in total these are the set of supported primitive types and constants, and then the table of primitive types follows, truncated in the version given here, so the full list is in the source.

The interesting design is the combination of that small core with the type class and unqualifier machinery. A language with a small primitive set and no runtime safety would be unusable without a way to extend operations, and that is what the Type Classes and Unqualifier Modules sections provide. Type classes give overloading resolved at compile time, and unqualifier modules are described as user-defined compiler plugins for custom constraint handling.

The net effect is a language that is statically typed, compiles to native code, and still supports the kind of generic-looking operations that a user writing filter expressions expects. The comprehension section is the practical expression of that, since transform, sort, join, filter and group over sequences are the operations a data analyst wants to type at a prompt.

Pattern matching is described as efficiently classifying and destructuring data, which is a better fit for structured records than positional access, and the parsing section provides LALR(1) grammars so that a user can define a syntax for their own data format rather than being forced into a fixed one.

## A research-grade repository with a real infrastructure

The repository layout gives a sense of how serious the project is.

There is an include/ directory for the public headers, lib/ for the implementation, test/ for the test suite, examples/ with a rosetta/ subdirectory, and doc/ for the documentation sources. There is a fuzz/ directory, a .clusterfuzzlite/ directory and an .oss-scanner/ directory, which together say the project is fuzzed in continuous fuzzing and scanned for dependency issues. The OSS-Fuzz badge in the README and the OpenSSF Scorecard badge are the visible evidence.

The build has three independent paths. CMakeLists.txt at the root is the primary one. There is a flake.nix and flake.lock with a nix/ directory and a default.nix, so the project can be built with Nix reproducibly. And there is a docker/ directory, plus a .devcontainer/ directory for working inside a containerised editor. There is a .gitlab-ci.yml alongside the .github/ directory, so CI runs in more than one place.

The governance files are notable: AGENTS.md, CONTRIBUTING.md, SECURITY.md, NOTICE.md, CODE_OF_CONDUCT conventions in the CI and a dco/ directory for a Developer Certificate of Origin. RELEASE_CODENAMES.tsv is a small detail that suggests releases are named.

The last push was on 2026-10-15, the README carries an Active lifecycle badge, and the repository has no GitHub releases, so the version history lives in the tag list and the changelog rather than in release entries.

## Conclusion

hobbes suits a team writing a C++ application that needs a user-facing expression language, a data recorder and an ad hoc query tool, and wants all three to run at native speed without shipping an interpreter to the client. It is the wrong tool if the expressions come from untrusted users, because the README states there is no sandboxed runtime and no array bounds checks, and it is the wrong choice for a scripting language aimed at script users rather than at C++ developers. Before you embed it, read the usage note in the README about direct memory access and remote code execution over RPC, and decide whether your expressions can ever originate outside your trust boundary.

## FAQ

### Is hobbes safe to evaluate untrusted expressions?

No. The README states that hobbes does not have a sandboxed runtime environment or runtime safety features, and that by design it gives direct access to memory without array bounds checks. It also supports compiling and executing native code remotely over RPC, which is meant for a trusted internal network only.

### What do I need to build hobbes?

LLVM 3.3 or later, cmake 3.19 or later, GNU gcc 4.8 or later, and a Linux kernel 2.5 or later. If cmake cannot find LLVM, set the LLVM_DIR environment variable to the directory holding LLVM's LLVMConfig.cmake file.

### How do I embed hobbes in a C++ program?

Construct a hobbes::cc object, call compileFn with the return type you expect and the expression string, and call the result. Compilation failures raise an exception with details, and you call hobbes::resetMemoryPool() afterwards to release memory allocated by the compiled expression.

### What is the difference between libhobbes.a and libhobbes-pic.a?

The build produces libhobbes.a for linking into a C++ executable, and a different static library, libhobbes-pic.a, for use in a .so, since position independent code is required for a shared object.

### What are the hi and hog programs?

The build produces two utilities. hi is a basic interactive interpreter for hobbes expressions, and hog efficiently records structured data produced by applications into data files which can be loaded and queried at the same time by hi.

## Sources

- [Issues](https://github.com/morganstanley/hobbes/issues)
- [License: Apache-2.0](https://github.com/morganstanley/hobbes/blob/main/LICENSE)
- [morganstanley/hobbes on GitHub](https://github.com/morganstanley/hobbes)
- [Project website](http://hobbes.readthedocs.io/)
- [README](https://github.com/morganstanley/hobbes/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/morganstanley-hobbes
