NV
NordVarg
ServicesTechnologiesIndustriesCase StudiesBlogAboutContact
Get Started

Footer

NV
NordVarg

Software Development & Consulting

GitHubLinkedInTwitter

Services

  • Product Development
  • Quantitative Finance
  • Financial Systems
  • ML & AI

Technologies

  • C++
  • Python
  • Rust
  • OCaml
  • TypeScript
  • React

Company

  • About
  • Case Studies
  • Blog
  • Contact

© 2025 NordVarg. All rights reserved.

August 12, 2024
•
NordVarg Team
•

Type Safety in Financial Systems: OCaml vs Rust

Comparing two languages that take type safety seriously, and why it matters for mission-critical financial applications

LanguagesOCamlRustType SafetyFunctional Programming
7 min read
Share:

Why Type Safety Matters in Finance#

A bug in a financial system can cost millions. Type safety catches entire classes of bugs at compile time:

  • Arithmetic errors: Mixing currencies, units
  • Null pointer errors: Billion-dollar mistake
  • Race conditions: Data races in concurrent code
  • State machine bugs: Invalid state transitions

Let's compare two languages that excel at type safety: OCaml and Rust.

The Contenders#

OCaml#

  • Functional-first, 30+ years old
  • Used by Jane Street for trading systems
  • Excellent type inference
  • Garbage collected

Rust#

  • Systems programming, 9 years old
  • Used by financial firms for performance-critical code
  • Ownership system prevents memory bugs
  • No garbage collector

Round 1: Preventing Arithmetic Errors#

OCaml: Phantom Types#

ocaml
1(* Define currency types *)
2type usd
3type eur
4type btc
5
6(* Money type parameterized by currency *)
7type 'currency money = Money of int64
8
9(* Constructor functions *)
10let usd (cents : int64) : usd money = Money cents
11let eur (cents : int64) : eur money = Money cents
12let btc (satoshis : int64) : btc money = Money satoshis
13
14(* Type-safe operations *)
15let add_usd (Money a : usd money) (Money b : usd money) : usd money =
16  Money (Int64.add a b)
17
18(* This won't compile! *)
19(* let wrong = add_usd (usd 100L) (eur 100L) *)
20(* Error: This expression has type eur money
21         but an expression was expected of type usd money *)
22

Rust: Newtype Pattern#

rust
1// Define currency types as zero-sized types
2struct USD;
3struct EUR;
4struct BTC;
5
6// Money type parameterized by currency
7struct Money<C> {
8    amount: i64,
9    _currency: PhantomData<C>,
10}
11
12impl Money<USD> {
13    fn dollars(amount: i64) -> Self {
14        Money { 
15            amount: amount * 100, // Store as cents
16            _currency: PhantomData 
17        }
18    }
19    
20    fn cents(amount: i64) -> Self {
21        Money { 
22            amount, 
23            _currency: PhantomData 
24        }
25    }
26}
27
28// Type-safe addition
29impl<C> std::ops::Add for Money<C> {
30    type Output = Money<C>;
31    
32    fn add(self, other: Self) -> Self::Output {
33        Money {
34            amount: self.amount + other.amount,
35            _currency: PhantomData,
36        }
37    }
38}
39
40// This won't compile!
41// let wrong = Money::<USD>::dollars(100) + Money::<EUR>::euros(100);
42// Error: mismatched types
43

Winner: Tie. Both prevent mixing currencies at compile time.

Round 2: Null Safety#

OCaml: Option Type#

ocaml
1(* No null in OCaml! Use option type *)
2type 'a option = 
3  | None 
4  | Some of 'a
5
6(* Finding a user *)
7let find_user (id : int) : user option =
8  match Database.query id with
9  | [] -> None
10  | u :: _ -> Some u
11
12(* Pattern matching forces handling both cases *)
13let process_user id =
14  match find_user id with
15  | None -> print_endline "User not found"
16  | Some user -> print_endline user.name
17

Rust: Option Type#

rust
1// No null in Rust either! Use Option<T>
2enum Option<T> {
3    None,
4    Some(T),
5}
6
7// Finding a user
8fn find_user(id: u64) -> Option<User> {
9    database::query(id)
10}
11
12// Pattern matching (exhaustive)
13fn process_user(id: u64) {
14    match find_user(id) {
15        None => println!("User not found"),
16        Some(user) => println!("{}", user.name),
17    }
18}
19
20// Or use combinators
21fn get_user_email(id: u64) -> Option<String> {
22    find_user(id)
23        .map(|user| user.email)
24        .filter(|email| !email.is_empty())
25}
26

Winner: Tie. Both eliminate null pointer exceptions entirely.

Round 3: State Machines#

OCaml: Variants + GADTs#

ocaml
1(* Order states *)
2type pending
3type validated
4type executed
5type cancelled
6
7(* Order type parameterized by state *)
8type _ order =
9  | Pending : { id : string; amount : int64 } -> pending order
10  | Validated : { id : string; amount : int64; risk_approved : bool } -> validated order
11  | Executed : { id : string; amount : int64; execution_price : int64 } -> executed order
12  | Cancelled : { id : string; reason : string } -> cancelled order
13
14(* State transitions only allow valid moves *)
15let validate (Pending o : pending order) : validated order =
16  Validated { 
17    id = o.id; 
18    amount = o.amount; 
19    risk_approved = check_risk o.amount 
20  }
21
22let execute (Validated o : validated order) price : executed order =
23  Executed { 
24    id = o.id; 
25    amount = o.amount; 
26    execution_price = price 
27  }
28
29(* This won't compile! *)
30(* let wrong = execute (Pending { id = "1"; amount = 100L }) 1000L *)
31(* Error: This expression has type pending order
32         but an expression was expected of type validated order *)
33

Rust: Typestate Pattern#

rust
1// State types
2struct Pending;
3struct Validated;
4struct Executed;
5struct Cancelled;
6
7// Order parameterized by state
8struct Order<S> {
9    id: String,
10    amount: i64,
11    state: PhantomData<S>,
12}
13
14// State-specific data
15struct ValidatedData {
16    risk_approved: bool,
17}
18
19struct ExecutedData {
20    execution_price: i64,
21}
22
23// State transitions
24impl Order<Pending> {
25    fn new(id: String, amount: i64) -> Self {
26        Order { id, amount, state: PhantomData }
27    }
28    
29    fn validate(self) -> (Order<Validated>, ValidatedData) {
30        let data = ValidatedData {
31            risk_approved: check_risk(self.amount),
32        };
33        let order = Order {
34            id: self.id,
35            amount: self.amount,
36            state: PhantomData,
37        };
38        (order, data)
39    }
40}
41
42impl Order<Validated> {
43    fn execute(self, price: i64) -> (Order<Executed>, ExecutedData) {
44        let data = ExecutedData {
45            execution_price: price,
46        };
47        let order = Order {
48            id: self.id,
49            amount: self.amount,
50            state: PhantomData,
51        };
52        (order, data)
53    }
54}
55
56// This won't compile!
57// let order = Order::<Pending>::new("1".into(), 100);
58// let (executed, _) = order.execute(1000); // Error!
59

Winner: OCaml. GADTs are more elegant than Rust's typestate pattern.

Round 4: Concurrency#

OCaml: Domains (OCaml 5.0+)#

ocaml
1(* No data races! Shared memory requires explicit synchronization *)
2let process_orders orders =
3  let results = ref [] in
4  let domains = List.map (fun order ->
5    Domain.spawn (fun () ->
6      let result = process_order order in
7      Mutex.protect mutex (fun () ->
8        results := result :: !results
9      )
10    )
11  ) orders in
12  List.iter Domain.join domains;
13  !results
14

Rust: Ownership + Send/Sync#

rust
1// No data races! Ownership prevents concurrent access
2fn process_orders(orders: Vec<Order>) -> Vec<Result> {
3    orders
4        .into_par_iter() // Parallel iterator
5        .map(|order| process_order(order))
6        .collect()
7}
8
9// Shared state requires explicit synchronization
10use std::sync::{Arc, Mutex};
11
12let counter = Arc::new(Mutex::new(0));
13let handles: Vec<_> = (0..10)
14    .map(|_| {
15        let counter = Arc::clone(&counter);
16        thread::spawn(move || {
17            let mut num = counter.lock().unwrap();
18            *num += 1;
19        })
20    })
21    .collect();
22
23// Won't compile without Arc + Mutex
24// let x = vec![1, 2, 3];
25// thread::spawn(move || println!("{:?}", x));
26// println!("{:?}", x); // Error: value moved
27

Winner: Rust. Ownership system prevents data races at compile time.

Round 5: Performance#

OCaml#

  • Garbage collected (some overhead)
  • Excellent for CPU-bound tasks
  • Multicore support (OCaml 5.0+)
  • 2-3x slower than C++ typically

Rust#

  • No garbage collector
  • Zero-cost abstractions
  • Predictable performance
  • Similar to C++ performance

Winner: Rust for predictable low-latency systems.

When to Use Which?#

Choose OCaml When:#

  • Rapid development is priority
  • Complex business logic
  • Type safety > raw performance
  • GC pauses acceptable (< 10ms)
  • Team has functional programming expertise

Examples:

  • Core banking systems
  • Trading risk systems
  • Pricing engines
  • Back-office processing

Choose Rust When:#

  • Performance is critical
  • Low-latency required (< 1ms)
  • Memory usage must be predictable
  • Interfacing with C/C++ libraries
  • Systems programming

Examples:

  • High-frequency trading
  • Market data feeds
  • Low-level protocols
  • Performance-critical components

Real-World Perspective#

At NordVarg, we've used both:

OCaml for a core banking system:

  • 100% uptime during migration from COBOL
  • Type safety caught 100+ bugs at compile time
  • Rapid iteration on business logic
  • GC pauses < 5ms acceptable for banking

Rust for risk calculations:

  • 100,000+ calculations per second
  • Predictable latency (P99 < 100μs)
  • No GC pauses
  • Safe parallelism for multi-core usage

Conclusion#

Both languages offer excellent type safety:

FeatureOCamlRust
Type Safety⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Performance⭐⭐⭐⭐⭐⭐⭐⭐⭐
Concurrency⭐⭐⭐⭐⭐⭐⭐⭐⭐
Ease of Use⭐⭐⭐⭐⭐⭐⭐
Ecosystem⭐⭐⭐⭐⭐⭐⭐

The right choice depends on your constraints.

Resources#

  • OCaml Manual
  • Rust Book
  • Real World OCaml
  • Rust for Rustaceans

Need help choosing the right language? Contact us for a consultation.

NT

NordVarg Team

Technical Writer

NordVarg Team is a software engineer at NordVarg specializing in high-performance financial systems and type-safe programming.

OCamlRustType SafetyFunctional Programming

Join 1,000+ Engineers

Get weekly insights on building high-performance financial systems, latest industry trends, and expert tips delivered straight to your inbox.

✓Weekly articles
✓Industry insights
✓No spam, ever

Related Posts

Jan 10, 2025•18 min read
Dependent Types in OCaml: Type-Level Programming with GADTs
Languagesocamldependent-types
Jan 5, 2025•18 min read
Type Providers in OCaml: Compile-Time Code Generation
Languagesocamltype-providers
Dec 31, 2024•10 min read
OCaml for Financial Modeling
Languagesocamlfunctional-programming

Interested in working together?