I am currently looking for a job! If you’re hiring a new grad in 2026 for for Rust, TypeScript, or React, feel free to shoot me an email at serena (at) quamserena.com.
I’ve been writing a compiler in OCaml for the language described by the dragon book for a class, and now that I’m about halfway through it I’ve found myself missing Rust in a few spots. In no particular order, the pain points I’ve hit while using OCaml:
Syntax
The OCaml syntax is... not great, its only redeeming quality being that it has no significant whitespace. There are some alternative syntax frontends for OCaml like ReasonML that fix these problems, but I didn’t know about them before starting the project. let...in syntax is weird, but I can chalk that up to an aesthetic choice; the real devil is i…
I am currently looking for a job! If you’re hiring a new grad in 2026 for for Rust, TypeScript, or React, feel free to shoot me an email at serena (at) quamserena.com.
I’ve been writing a compiler in OCaml for the language described by the dragon book for a class, and now that I’m about halfway through it I’ve found myself missing Rust in a few spots. In no particular order, the pain points I’ve hit while using OCaml:
Syntax
The OCaml syntax is... not great, its only redeeming quality being that it has no significant whitespace. There are some alternative syntax frontends for OCaml like ReasonML that fix these problems, but I didn’t know about them before starting the project. let...in syntax is weird, but I can chalk that up to an aesthetic choice; the real devil is in the fact that match statements have no terminator, leading to mysterious errors sometimes if you try to nest them. In general, OCaml has very little punctuation (which isn’t necessarily a bad thing) but it can mean that in some situations it’s difficult to tell where this start and stop. For me, C-style brackets help make the syntax heirarchy in code more clear.
Another annoyance is automatic currying and partial function application. Usually partial function application is a bug, and typically there is a helpful lint where it happens, but if you’re not explicitly annotating types the errors can be inscrutable. My preferred method for debugging syntax-related issues is to start randomly adding parenthesis where I think things are supposed to start and end, and usually that makes it clear where the error is.
Typechecking is way too clever
The type checker in OCaml is extremely clever and is usually able to determine the correct type of a variable without having to annotate anything. When it can’t, though, (usually because of an error you made somewhere else) the error message is typically inscrutable. While this is cool because it allows OCaml to be both statically typed and have no type annotations, it is extremely bad from a software robustness perspective — usage of a variable in one area can influence its type in another opaquely, and it’s just annoying to debug why the type inference is failing. Good luck debugging this when one of your dependencies updates and changes a function’s signature. I’ve taken to annotating every function parameter and return type manually just for clarity’s sake and so that the error messages make more sense. I think that Rust was definitely correct for making type annotations for function parameters and return values explicit despite also being based on Hindley-Milner type inference. Rust still allows you to be clever with type inference in some places if you want to though, such as with return type polymorphism.
Types
Annoyingly you cannot use types before they’re declared — there is no type hoisting as you would see in other languages. You can get around this pretty easily by creating a parameterized type and then at the bottom of the file setting an alias for the type with the correct parameter bound, but it adds noise to the code. Enumerated types also dump all of their variants into the module scope (not namespaced to any type) and will silently shadow variants from other types, which is an odd choice to say the least. I think that it is reminiscent of global #define’s not being namespaced in C, but I am not sure.
Tooling and ecosystem
OCaml tooling has improved a lot recently. It has a build system, Dune, that is comparable to the build systems of other languages like Cargo or NPM, and generally works quite well in my experience. The OCaml ecosystem is a bit strange, being dominated by Jane Street who maintains their own separate standard library Core (since the OCaml stdlib leaves a lot to be desired).
In terms of writing compilers, there’s ocamllex and Menhir, which is what I’m using. (Menhir is (mostly) a drop-in replacement for ocamlyacc that fixes some problems). My gripe with these is that they are two DSLs that opaquely compile to OCaml source files and have their own (separate) semantics and grammar. Despite OCaml having match statements and everything, these tools instead more similarly follow their C counterparts, choosing to define their own match-statement-like-construct for lexing and parsing. They also emit errors where the entire diagnostic is just “Syntax Error”, which is difficult to debug considering that these are DSLs with OCaml embedded within them. Usually the only way to debug this is to open up the Menhir manual and look at the examples.
Printing
Printing strings to stdout is oddly annoying in OCaml. There isn’t really a way that you can just take an object and print it; instead you have to use printf for primitives and define your own print function for your own objects — there is no equivalent to Java’s toString or Rust’s #[derive(Debug)]. The inability to express this is a weakness of the OCaml type system in my opinion.
Would I do it again?
When comparing Rust to OCaml, there is a general theme of robustness vs. elegance. Many of the design choices of OCaml (partial function application, inferred types) are motivated from the perspective of elegant functional programming (the things a PhD student cares about), meanwhile Rust is always about long-term stability and maintainability (the things a software engineer cares about). The usual mantra applies here: that neither of these are better in the general case but rather the best choice depends on the problem domain.
If I was to write another compiler, I would highly consider OCaml again, despite all of its pain points and missing quality-of-life features. I enjoy being able to use pattern matching and the elegance of a functional approach without having to worry about lifetimes and memory management, at least for a first draft/MVP version of the compiler. I would probably take a closer look at ReasonML though.