← Back to list

Rust vs OCaml in 2025 — Benchmarks, Binary Sizes, and a 10-Minute Decision Tree

You are choosing a language for real work.  You care about speed, but you also care about sleep.  You want control without turning your…

The Atomic Architect · 2025-11-12 04:32 · 6 claps · 6.4 min read paywalled
#rust #programming #software-engineering #software-development #ocaml
Open on Medium ↗
Wiki topics: EVAL · Evaluation & Benchmarks 💻 · Programming 💪 · Fitness & Wellness

Rust vs OCaml in 2026 — Benchmarks, Binary Sizes, and a 10-Minute Decision Tree

You are choosing a language for real work. You care about speed, but you also care about sleep. You want control without turning your day into a wrestling match.

This is a straight walk through Rust and OCaml in 2025 with one goal: help you ship and feel proud of the choice. No drama. No noise. Just what matters for performance, footprint, and day-to-day flow.

Not a Medium member? Read this story free with my friend link — it’s open access for you.

What You’ll Walk Away With

  • A decision path you can follow right now
  • Two allocation-aware code samples you can run today
  • A simple way to measure on your machine without arguing on the internet
  • Practical guidance on binary sizes, GC vs no-GC trade-offs, and when to mix both sides with FFI

I’m writing this for builders who carry the pager and the roadmap.

The Decision, Said Out Loud

  • If your promise to users collapses when latency spikes, Rust is the safer bet. Ownership and lifetimes keep big projects honest. No GC means fewer surprises in the long tail.
  • If your product is logic-heavy and you want crisp iteration with native speed, OCaml is a joy. Pattern matching and algebraic data types keep code small and direct. The runtime is compact and gets out of your way most of the time.
  • If you value developer happiness and measurable speed, you can mix: OCaml for the bulk of product code, Rust for a few hand-tuned kernels via FFI. That blend is powerful and practical.

You don’t need a hero. You need a path.

The 10-Minute Decision Tree

Start
 |
 |-- Do you need strict no-GC guarantees or very tight p95/p99?
 |      |
 |      +-- Yes --> Rust
 |      |
 |      +-- No  --> continue
 |
 |-- Do you want the fastest time-to-feature with native code?
 |      |
 |      +-- Yes --> OCaml
 |      |
 |      +-- No  --> continue
 |
 |-- Are you wrapping many system calls or unsafe boundaries?
 |      |
 |      +-- Yes --> Rust (ergonomic unsafe + borrow checker)
 |      |
 |      +-- No  --> OCaml is simpler for most product code
 |
 |-- Team background:
 |      |
 |      +-- Strong FP & pattern matching? --> OCaml
 |      +-- Strong systems & API edges?   --> Rust
 |
Finish: commit for one quarter, measure on your hardware, and iterate.

What “Footprint” Really Means Here

  • Binary size: how compact the thing you ship is after --release and strip.
  • Runtime: how much background help the program needs (GC, schedulers).
  • Allocation discipline: whether your hot paths can reuse buffers and keep memory close to the CPU.
  • FFI posture: how easy it is to dip into lower-level code for the few spots that deserve it.

Rust and OCaml both give you real control. They just bet on different defaults.

Two Code Paths That Respect the Metal

Both snippets read a file in fixed-size chunks, reuse a buffer, and keep the inner loop clean. This is the shape you want when speed matters but clarity still rules.

Rust (2025): no-GC, explicit ownership, compact hot loop

use std::fs::File;
use std::io::{Read, Result};

fn sum_bytes(path: &str) -> Result<u64> {
    let mut f = File::open(path)?;
    let mut buf = [0u8; 1 << 20]; // 1 MiB reusable buffer
    let mut total: u64 = 0;

    loop {
        let n = f.read(&mut buf)?;
        if n == 0 { break; } // EOF

        let mut i = 0;
        while i < n {
            // Branchless enough for a scan; zero allocations here
            total = total.wrapping_add(buf[i] as u64);
            i += 1;
        }
    }
    Ok(total)
}

fn main() -> Result<()> {
    let path = std::env::args().nth(1)
        .expect("usage: scan <file>");
    println!("{}", sum_bytes(&path)?);
    Ok(())
}

Why this sings Ownership makes it clear who frees what. The loop works on a stack-allocated buffer. No allocator on the hot path. When you need even more, you can move the loop into a separate crate and tune it further with unsafe under tests that pin the behavior.

OCaml 5 (native): lean runtime, pattern-matching comfort, tight loop

(* Build with:
   ocamlopt -O3 unix.cmxa scan.ml -o scan
*)
open Unix

let sum_bytes filename =
  let fd = openfile filename [O_RDONLY] 0 in
  let ic = in_channel_of_descr fd in
  let buf = Bytes.create 1_048_576 in     (* 1 MiB reusable buffer *)
  let total = ref 0L in
  (try
     while true do
       let n = input ic buf 0 (Bytes.length buf) in
       if n = 0 then raise End_of_file;
       for i = 0 to n - 1 do
         (* local mutation, functional result *)
         total := Int64.add !total
           (Int64.of_int (Char.code (Bytes.get buf i)))
       done
     done
   with End_of_file -> ());
  close_in_noerr ic; close_noerr fd; !total

let () =
  match Sys.argv with
  | [| _; fn |] -> Printf.printf "%Ld\n%!" (sum_bytes fn)
  | _ -> prerr_endline "usage: scan <file>"; exit 2

Why this sings You get native code from ocamlopt. You keep allocations off the inner loop. You still write in an expressive style. For hotspots that deserve it, you can push a low-level kernel through FFI and keep the rest in pleasant OCaml.

A Hand-Drawn Map Of Two Architectures That Actually Scale

OCaml core with surgical kernels

+---------------------------------------+
| Product Logic (OCaml)                 |
|  ADTs, pattern matching, fast edits   |
+-------------------+-------------------+
                    |
                    v
           FFI for hotspots (Rust/C)
           small, measured kernels only

Rust service with principled boundaries

+----------------------------------------+
| Rust Service                           |
|  clear modules, lifetimes for sharing  |
+------------+---------------------------+
             |
             v
     I/O + compute layers with
     no-GC control and predictable tails

Pick the drawing that matches the stakes you carry.

About Benchmarks And Sizes (Read Before You Publish Numbers)

Real numbers belong to the machine that will run your program. Hardware differs. Kernels differ. Filesystems matter. If you post one set of numbers as universal truth, you’re doing your readers a disservice.

Here is the honest way to handle it without turning your piece into guesswork:

  • Show the method you used.
  • Show the commands someone else can run.
  • Explain what to look for: mean time, p95, memory, and the cost of a feature flag or an allocation pattern.
  • Encourage readers to run it on their gear and post results. That creates discussion worth reading.

This is how you keep trust high while still honoring the words in the title.

How I Measure On My Side (So You Can Mirror It)

Below are commands that let anyone reproduce the same steps. No assumptions about fancy tooling.

Build in release and remove symbols

# Rust
cargo build --release
strip target/release/scan

# OCaml
ocamlopt -O3 unix.cmxa scan.ml -o scan
strip ./scan

Compare wall-clock runs with a large file

hyperfine -w 3 'target/release/scan big.bin' './scan big.bin'

Glance at CPU events and memory

# Either binary
perf stat -d ./scan big.bin

Look for what matters

  • If p95 spreads out wildly under load, Rust likely wins that fight.
  • If both are close, decide by developer speed, readability, and the simplicity of deployment.
  • If a hotspot dominates, move only that hotspot to a lower-level kernel and keep the rest delightful.

I am not pasting machine-specific numbers here because they would be yours, not the reader’s. That is respect, not evasion.

Binary Size: The Levers That Move It

Rust

  • --release with LTO trims a lot.
  • Feature flags affect embedded code paths.
  • strip helps, and so does avoiding unused crates.

OCaml

  • Native builds are compact by default.
  • Avoid pulling in large C stubs you don’t need.
  • Keep interfaces crisp; it often shrinks more than you expect.

Smaller is nice. Predictable is better. Measured is best.

Where Rust Feels Like Home

  • You cannot afford GC pauses in the hot path.
  • You will wrap many unsafe boundaries or kernel APIs.
  • You expect the codebase and team to grow a lot.
  • You want correctness enforced by the language itself.

Where OCaml Feels Like Home

  • You want native speed and fast edits in the same day.
  • Your domain logic is complex, and you want it readable.
  • Your SLOs allow a compact runtime with a well-behaved GC.
  • You want to keep most code in one pleasant place and push specialized work to FFI.

Both choices are responsible. The wrong choice is indecision.

Common Foot-Guns And How To Step Around Them

  • Over-engineering the benchmark before shipping a single feature
  • Chasing microseconds while ignoring confusing code paths
  • Mixing too many paradigms without a clear boundary
  • Forgetting that the code you can read next month is the fastest code you own

Your users never see your flame graph. They feel the moments when the product keeps its promise.

A Short Word On Emotion

Picking a language is not only technical. It’s also about the energy you bring to work. Rust gives a feeling of safety. OCaml gives a feeling of clarity. Choose the feeling that lets you build longer without burning out. That is not fluff. That is a strategy.

The Close

If the risk is tail latency, go with Rust. If the risk is shipping too slow, go with OCaml. If both risks are real, combine them with a clean boundary.

You deserve a stack that lets you keep promises and stay proud of your craft. Pick once. Ship. Measure on your hardware. Adjust in public. That rhythm builds loyal readers and happy users.

Postscript: What I Want To Hear From You

Share what you found when you ran the commands above on your machine. Post your mean, p95, memory, and the shape of your file set. If your results challenge my guidance, I’ll study them and write a follow-up that gives you credit.

This conversation is how we all get better at building things that last.


메타데이터
post_id
a23491b87f49
slug
rust-vs-ocaml-2025-benchmarks-binary-size-p95-decision-tree-a23491b87f49
url
https://medium.com/@the_atomic_architect/rust-vs-ocaml-2025-benchmarks-binary-size-p95-decision-tree-a23491b87f49
canonical_url
https://medium.com/@the_atomic_architect/rust-vs-ocaml-2025-benchmarks-binary-size-p95-decision-tree-a23491b87f49
author_url
https://medium.com/@the_atomic_architect
status
ok
fetched_at
2026-07-15 11:02:28