Cross-Language Interfacing: Calling C/C++ from Rust, OCaml, and Python
Building high-performance systems by combining languages—practical patterns for FFI, safety, and zero-cost abstractions
Real-world systems rarely exist in a single language. Legacy C/C++ libraries contain decades of optimized algorithms. Python offers rapid development. Rust provides memory safety. OCaml delivers type safety and performance. The key to building robust systems is knowing how to interface these languages effectively.
We've built trading platforms that combine all four languages: C++ for ultra-low latency components, Rust for system services, OCaml for risk engines, and Python for research. This post shares the techniques, pitfalls, and best practices we've learned.
Each language has strengths:
| Language | Strengths | Use Cases | FFI Complexity |
|---|---|---|---|
| C/C++ | Raw performance, hardware control | Hot paths, device drivers | N/A (base layer) |
| Rust | Safety + performance, modern tooling | System services, infrastructure | Low (great FFI) |
| OCaml | Type safety, GC, fast compilation | Business logic, compilers | Medium |
| Python | Rapid development, huge ecosystem | Research, scripting, ML | Low (ctypes, cffi) |
The art is knowing when to use each and how to make them work together seamlessly.
Common patterns for multi-language systems:
1┌─────────────────────────────────────────────────┐
2│ Python Research Layer │
3│ (NumPy, Pandas, Jupyter, ML frameworks) │
4└────────────┬────────────────────────────────────┘
5 │ FFI (ctypes/pybind11)
6┌────────────▼────────────────────────────────────┐
7│ Rust Service Layer │
8│ (Web servers, message queues, monitoring) │
9└────────────┬────────────────────────────────────┘
10 │ FFI (extern "C")
11┌────────────▼────────────────────────────────────┐
12│ OCaml Business Logic │
13│ (Risk calculations, order routing) │
14└────────────┬────────────────────────────────────┘
15 │ FFI (Ctypes library)
16┌────────────▼────────────────────────────────────┐
17│ C/C++ Performance Critical Code │
18│ (Market data parsing, pricing algorithms) │
19└─────────────────────────────────────────────────┘
20C has the most stable ABI, making it the lingua franca:
1// mathlib.h - C interface to C++ implementation
2#ifndef MATHLIB_H
3#define MATHLIB_H
4
5#include <stdint.h>
6#include <stdbool.h>
7
8#ifdef __cplusplus
9extern "C" {
10#endif
11
12// Opaque handle (hides C++ implementation)
13typedef struct PricingEngine PricingEngine;
14
15// C API functions
16PricingEngine* pricing_engine_create(const char* model_type);
17void pricing_engine_destroy(PricingEngine* engine);
18
19double pricing_engine_calculate(
20 PricingEngine* engine,
21 double spot,
22 double strike,
23 double volatility,
24 double time_to_expiry,
25 double risk_free_rate
26);
27
28bool pricing_engine_calibrate(
29 PricingEngine* engine,
30 const double* market_prices,
31 size_t num_prices
32);
33
34#ifdef __cplusplus
35}
36#endif
37
38#endif // MATHLIB_H
39The C++ implementation:
1// mathlib.cpp - C++ implementation with C wrapper
2#include "mathlib.h"
3#include <memory>
4#include <vector>
5#include <cmath>
6
7// C++ implementation class
8class BlackScholesPricer {
9public:
10 explicit BlackScholesPricer(const std::string& model_type)
11 : model_type_(model_type) {}
12
13 double calculatePrice(
14 double spot,
15 double strike,
16 double volatility,
17 double timeToExpiry,
18 double riskFreeRate
19 ) const {
20 // Black-Scholes formula
21 double d1 = (std::log(spot / strike) +
22 (riskFreeRate + 0.5 * volatility * volatility) * timeToExpiry) /
23 (volatility * std::sqrt(timeToExpiry));
24 double d2 = d1 - volatility * std::sqrt(timeToExpiry);
25
26 return spot * normalCDF(d1) - strike * std::exp(-riskFreeRate * timeToExpiry) * normalCDF(d2);
27 }
28
29 bool calibrate(const std::vector<double>& marketPrices) {
30 // Calibration logic
31 calibrated_ = true;
32 return true;
33 }
34
35private:
36 std::string model_type_;
37 bool calibrated_ = false;
38
39 // Standard normal cumulative distribution function
40 static double normalCDF(double x) {
41 return 0.5 * std::erfc(-x / std::sqrt(2.0));
42 }
43};
44
45// C wrapper implementation
46extern "C" {
47 PricingEngine* pricing_engine_create(const char* model_type) {
48 try {
49 auto* engine = new BlackScholesPricer(model_type);
50 return reinterpret_cast<PricingEngine*>(engine);
51 } catch (...) {
52 return nullptr;
53 }
54 }
55
56 void pricing_engine_destroy(PricingEngine* engine) {
57 if (engine) {
58 auto* pricer = reinterpret_cast<BlackScholesPricer*>(engine);
59 delete pricer;
60 }
61 }
62
63 double pricing_engine_calculate(
64 PricingEngine* engine,
65 double spot,
66 double strike,
67 double volatility,
68 double time_to_expiry,
69 double risk_free_rate
70 ) {
71 if (!engine) return -1.0;
72
73 auto* pricer = reinterpret_cast<BlackScholesPricer*>(engine);
74 return pricer->calculatePrice(spot, strike, volatility, time_to_expiry, risk_free_rate);
75 }
76
77 bool pricing_engine_calibrate(
78 PricingEngine* engine,
79 const double* market_prices,
80 size_t num_prices
81 ) {
82 if (!engine || !market_prices) return false;
83
84 auto* pricer = reinterpret_cast<BlackScholesPricer*>(engine);
85 std::vector<double> prices(market_prices, market_prices + num_prices);
86 return pricer->calibrate(prices);
87 }
88}
89Rust's FFI is excellent—compile-time safety with zero runtime overhead:
1// build.rs - Build script to compile C++ library
2fn main() {
3 cc::Build::new()
4 .cpp(true)
5 .file("src/mathlib.cpp")
6 .flag("-std=c++17")
7 .flag("-O3")
8 .compile("mathlib");
9
10 println!("cargo:rustc-link-lib=static=mathlib");
11 println!("cargo:rerun-if-changed=src/mathlib.cpp");
12 println!("cargo:rerun-if-changed=src/mathlib.h");
13}
14The safe Rust wrapper:
1// lib.rs - Safe Rust wrapper around C API
2use std::ffi::{CString, c_char, c_void};
3use std::ptr;
4
5// Raw FFI declarations (unsafe)
6#[repr(C)]
7struct CPricingEngine {
8 _private: [u8; 0],
9}
10
11extern "C" {
12 fn pricing_engine_create(model_type: *const c_char) -> *mut CPricingEngine;
13 fn pricing_engine_destroy(engine: *mut CPricingEngine);
14 fn pricing_engine_calculate(
15 engine: *mut CPricingEngine,
16 spot: f64,
17 strike: f64,
18 volatility: f64,
19 time_to_expiry: f64,
20 risk_free_rate: f64,
21 ) -> f64;
22 fn pricing_engine_calibrate(
23 engine: *mut CPricingEngine,
24 market_prices: *const f64,
25 num_prices: usize,
26 ) -> bool;
27}
28
29// Safe Rust wrapper
30pub struct PricingEngine {
31 engine: *mut CPricingEngine,
32}
33
34impl PricingEngine {
35 /// Create a new pricing engine
36 pub fn new(model_type: &str) -> Result<Self, &'static str> {
37 let c_model_type = CString::new(model_type)
38 .map_err(|_| "Invalid model type string")?;
39
40 let engine = unsafe { pricing_engine_create(c_model_type.as_ptr()) };
41
42 if engine.is_null() {
43 return Err("Failed to create pricing engine");
44 }
45
46 Ok(PricingEngine { engine })
47 }
48
49 /// Calculate option price
50 pub fn calculate_price(
51 &self,
52 spot: f64,
53 strike: f64,
54 volatility: f64,
55 time_to_expiry: f64,
56 risk_free_rate: f64,
57 ) -> f64 {
58 unsafe {
59 pricing_engine_calculate(
60 self.engine,
61 spot,
62 strike,
63 volatility,
64 time_to_expiry,
65 risk_free_rate,
66 )
67 }
68 }
69
70 /// Calibrate engine to market prices
71 pub fn calibrate(&mut self, market_prices: &[f64]) -> Result<(), &'static str> {
72 let success = unsafe {
73 pricing_engine_calibrate(
74 self.engine,
75 market_prices.as_ptr(),
76 market_prices.len(),
77 )
78 };
79
80 if success {
81 Ok(())
82 } else {
83 Err("Calibration failed")
84 }
85 }
86}
87
88// RAII: automatic cleanup when dropped
89impl Drop for PricingEngine {
90 fn drop(&mut self) {
91 unsafe {
92 pricing_engine_destroy(self.engine);
93 }
94 }
95}
96
97// Thread safety
98unsafe impl Send for PricingEngine {}
99unsafe impl Sync for PricingEngine {}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn test_pricing_engine() {
107 let engine = PricingEngine::new("black_scholes").unwrap();
108
109 let price = engine.calculate_price(
110 100.0, // spot
111 100.0, // strike
112 0.2, // volatility
113 1.0, // time to expiry
114 0.05, // risk-free rate
115 );
116
117 assert!(price > 0.0);
118 assert!(price < 100.0);
119 }
120
121 #[test]
122 fn test_calibration() {
123 let mut engine = PricingEngine::new("black_scholes").unwrap();
124
125 let market_prices = vec![10.5, 12.3, 15.7, 18.2];
126 engine.calibrate(&market_prices).unwrap();
127 }
128}
129Passing arrays without copying:
1/// Pass Rust slice to C without copying
2pub fn process_large_array(data: &[f64]) -> Vec<f64> {
3 let mut result = vec![0.0; data.len()];
4
5 unsafe {
6 // C function that operates on raw pointers
7 extern "C" {
8 fn c_transform_array(
9 input: *const f64,
10 output: *mut f64,
11 len: usize,
12 );
13 }
14
15 c_transform_array(
16 data.as_ptr(),
17 result.as_mut_ptr(),
18 data.len(),
19 );
20 }
21
22 result
23}
24
25/// Callback from C to Rust
26pub extern "C" fn rust_callback(value: f64, context: *mut c_void) -> f64 {
27 // Cast context back to Rust type
28 let ctx = unsafe { &mut *(context as *mut MyContext) };
29
30 // Safe Rust code
31 ctx.process(value)
32}
33
34struct MyContext {
35 multiplier: f64,
36}
37
38impl MyContext {
39 fn process(&mut self, value: f64) -> f64 {
40 value * self.multiplier
41 }
42}
43OCaml's Ctypes library provides excellent type safety:
1(* pricing_bindings.ml - OCaml bindings using Ctypes *)
2open Ctypes
3open Foreign
4
5(* Opaque type for the C struct *)
6type pricing_engine
7let pricing_engine : pricing_engine structure typ = structure "PricingEngine"
8
9(* Function bindings *)
10let create =
11 foreign "pricing_engine_create"
12 (string @-> returning (ptr_opt pricing_engine))
13
14let destroy =
15 foreign "pricing_engine_destroy"
16 (ptr pricing_engine @-> returning void)
17
18let calculate =
19 foreign "pricing_engine_calculate"
20 (ptr pricing_engine @-> double @-> double @-> double @->
21 double @-> double @-> returning double)
22
23let calibrate =
24 foreign "pricing_engine_calibrate"
25 (ptr pricing_engine @-> ptr double @-> size_t @-> returning bool)
26
27(* Safe OCaml wrapper *)
28module PricingEngine = struct
29 type t = pricing_engine Ctypes.structure Ctypes.ptr
30
31 let create model_type =
32 match create model_type with
33 | Some engine -> engine
34 | None -> failwith "Failed to create pricing engine"
35
36 let destroy = destroy
37
38 let calculate engine ~spot ~strike ~volatility ~time_to_expiry ~risk_free_rate =
39 calculate engine spot strike volatility time_to_expiry risk_free_rate
40
41 let calibrate engine market_prices =
42 let arr = CArray.of_list double market_prices in
43 let len = List.length market_prices in
44 calibrate engine (CArray.start arr) (Unsigned.Size_t.of_int len)
45
46 (* RAII pattern using finalize *)
47 let with_engine model_type f =
48 let engine = create model_type in
49 Fun.protect
50 ~finally:(fun () -> destroy engine)
51 (fun () -> f engine)
52end
53
54(* Usage example *)
55let example () =
56 PricingEngine.with_engine "black_scholes" (fun engine ->
57 (* Calculate call option price *)
58 let price = PricingEngine.calculate engine
59 ~spot:100.0
60 ~strike:100.0
61 ~volatility:0.2
62 ~time_to_expiry:1.0
63 ~risk_free_rate:0.05
64 in
65
66 (* Calibrate to market data *)
67 let market_prices = [10.5; 12.3; 15.7; 18.2] in
68 let success = PricingEngine.calibrate engine market_prices in
69
70 Printf.printf "Option price: %.2f\n" price;
71 Printf.printf "Calibration: %s\n" (if success then "success" else "failed")
72 )
73Passing OCaml functions to C:
1(* Callback from C to OCaml *)
2open Ctypes
3open Foreign
4
5(* Type for callback function *)
6type optimization_callback = float -> float
7
8(* Register callback with C library *)
9let optimize_function =
10 foreign "c_optimize"
11 (funptr (double @-> returning double) @->
12 double @-> double @->
13 returning double)
14
15(* OCaml function to optimize *)
16let quadratic x = x *. x -. 4.0 *. x +. 3.0
17
18(* Find minimum *)
19let find_minimum () =
20 let min_x = optimize_function quadratic (-10.0) 10.0 in
21 Printf.printf "Minimum at x = %.4f\n" min_x
221# pricing_ctypes.py - Using ctypes (no compilation needed)
2import ctypes
3from ctypes import c_double, c_char_p, c_void_p, c_bool, c_size_t, POINTER
4import numpy as np
5from typing import List
6
7# Load the shared library
8lib = ctypes.CDLL('./libmathlib.so')
9
10# Define function signatures
11lib.pricing_engine_create.argtypes = [c_char_p]
12lib.pricing_engine_create.restype = c_void_p
13
14lib.pricing_engine_destroy.argtypes = [c_void_p]
15lib.pricing_engine_destroy.restype = None
16
17lib.pricing_engine_calculate.argtypes = [
18 c_void_p, c_double, c_double, c_double, c_double, c_double
19]
20lib.pricing_engine_calculate.restype = c_double
21
22lib.pricing_engine_calibrate.argtypes = [
23 c_void_p, POINTER(c_double), c_size_t
24]
25lib.pricing_engine_calibrate.restype = c_bool
26
27class PricingEngine:
28 """Python wrapper for C pricing engine"""
29
30 def __init__(self, model_type: str):
31 self._engine = lib.pricing_engine_create(model_type.encode('utf-8'))
32 if not self._engine:
33 raise RuntimeError(f"Failed to create pricing engine: {model_type}")
34
35 def __del__(self):
36 if hasattr(self, '_engine') and self._engine:
37 lib.pricing_engine_destroy(self._engine)
38
39 def __enter__(self):
40 return self
41
42 def __exit__(self, exc_type, exc_val, exc_tb):
43 self.__del__()
44
45 def calculate_price(
46 self,
47 spot: float,
48 strike: float,
49 volatility: float,
50 time_to_expiry: float,
51 risk_free_rate: float
52 ) -> float:
53 """Calculate option price using Black-Scholes"""
54 return lib.pricing_engine_calculate(
55 self._engine,
56 c_double(spot),
57 c_double(strike),
58 c_double(volatility),
59 c_double(time_to_expiry),
60 c_double(risk_free_rate)
61 )
62
63 def calibrate(self, market_prices: List[float]) -> bool:
64 """Calibrate engine to market prices"""
65 # Convert to C array
66 prices_array = (c_double * len(market_prices))(*market_prices)
67
68 return lib.pricing_engine_calibrate(
69 self._engine,
70 prices_array,
71 c_size_t(len(market_prices))
72 )
73
74 def calculate_portfolio(
75 self,
76 spots: np.ndarray,
77 strikes: np.ndarray,
78 volatilities: np.ndarray,
79 times_to_expiry: np.ndarray,
80 risk_free_rate: float
81 ) -> np.ndarray:
82 """Vectorized calculation for portfolio"""
83 n = len(spots)
84 results = np.zeros(n)
85
86 for i in range(n):
87 results[i] = self.calculate_price(
88 spots[i],
89 strikes[i],
90 volatilities[i],
91 times_to_expiry[i],
92 risk_free_rate
93 )
94
95 return results
96
97# Usage
98def example():
99 with PricingEngine("black_scholes") as engine:
100 # Single calculation
101 price = engine.calculate_price(
102 spot=100.0,
103 strike=100.0,
104 volatility=0.2,
105 time_to_expiry=1.0,
106 risk_free_rate=0.05
107 )
108 print(f"Option price: {price:.2f}")
109
110 # Calibration
111 market_prices = [10.5, 12.3, 15.7, 18.2]
112 success = engine.calibrate(market_prices)
113 print(f"Calibration: {'success' if success else 'failed'}")
114
115 # Portfolio calculation
116 n = 1000
117 spots = np.random.uniform(90, 110, n)
118 strikes = np.full(n, 100.0)
119 vols = np.random.uniform(0.15, 0.25, n)
120 times = np.random.uniform(0.5, 2.0, n)
121
122 import time
123 start = time.time()
124 prices = engine.calculate_portfolio(spots, strikes, vols, times, 0.05)
125 elapsed = time.time() - start
126
127 print(f"Calculated {n} prices in {elapsed*1000:.2f}ms")
128 print(f"Average price: {prices.mean():.2f}")
1291// pricing_pybind.cpp - PyBind11 bindings for modern C++
2#include <pybind11/pybind11.h>
3#include <pybind11/stl.h>
4#include <pybind11/numpy.h>
5#include <vector>
6#include <cmath>
7
8namespace py = pybind11;
9
10class BlackScholesPricer {
11public:
12 BlackScholesPricer(const std::string& model_type)
13 : model_type_(model_type) {}
14
15 double calculatePrice(
16 double spot,
17 double strike,
18 double volatility,
19 double timeToExpiry,
20 double riskFreeRate
21 ) const {
22 double d1 = (std::log(spot / strike) +
23 (riskFreeRate + 0.5 * volatility * volatility) * timeToExpiry) /
24 (volatility * std::sqrt(timeToExpiry));
25 double d2 = d1 - volatility * std::sqrt(timeToExpiry);
26
27 return spot * normalCDF(d1) -
28 strike * std::exp(-riskFreeRate * timeToExpiry) * normalCDF(d2);
29 }
30
31 bool calibrate(const std::vector<double>& marketPrices) {
32 calibrated_ = true;
33 return true;
34 }
35
36 // Vectorized calculation using NumPy arrays
37 py::array_t<double> calculatePortfolio(
38 py::array_t<double> spots,
39 py::array_t<double> strikes,
40 py::array_t<double> volatilities,
41 py::array_t<double> timesToExpiry,
42 double riskFreeRate
43 ) const {
44 auto spots_buf = spots.request();
45 auto strikes_buf = strikes.request();
46 auto vols_buf = volatilities.request();
47 auto times_buf = timesToExpiry.request();
48
49 size_t n = spots_buf.size;
50
51 auto result = py::array_t<double>(n);
52 auto result_buf = result.request();
53
54 double* spots_ptr = static_cast<double*>(spots_buf.ptr);
55 double* strikes_ptr = static_cast<double*>(strikes_buf.ptr);
56 double* vols_ptr = static_cast<double*>(vols_buf.ptr);
57 double* times_ptr = static_cast<double*>(times_buf.ptr);
58 double* result_ptr = static_cast<double*>(result_buf.ptr);
59
60 // Parallel calculation
61 #pragma omp parallel for
62 for (size_t i = 0; i < n; ++i) {
63 result_ptr[i] = calculatePrice(
64 spots_ptr[i],
65 strikes_ptr[i],
66 vols_ptr[i],
67 times_ptr[i],
68 riskFreeRate
69 );
70 }
71
72 return result;
73 }
74
75private:
76 std::string model_type_;
77 bool calibrated_ = false;
78
79 static double normalCDF(double x) {
80 return 0.5 * std::erfc(-x / std::sqrt(2.0));
81 }
82};
83
84PYBIND11_MODULE(pricing_cpp, m) {
85 m.doc() = "Fast option pricing using C++";
86
87 py::class_<BlackScholesPricer>(m, "PricingEngine")
88 .def(py::init<const std::string&>())
89 .def("calculate_price", &BlackScholesPricer::calculatePrice,
90 "Calculate single option price",
91 py::arg("spot"),
92 py::arg("strike"),
93 py::arg("volatility"),
94 py::arg("time_to_expiry"),
95 py::arg("risk_free_rate"))
96 .def("calibrate", &BlackScholesPricer::calibrate,
97 "Calibrate to market prices",
98 py::arg("market_prices"))
99 .def("calculate_portfolio", &BlackScholesPricer::calculatePortfolio,
100 "Vectorized portfolio calculation",
101 py::arg("spots"),
102 py::arg("strikes"),
103 py::arg("volatilities"),
104 py::arg("times_to_expiry"),
105 py::arg("risk_free_rate"));
106}
107Python usage with PyBind11:
1# pricing_pybind_usage.py
2import numpy as np
3import pricing_cpp # Compiled PyBind11 module
4import time
5
6def benchmark():
7 engine = pricing_cpp.PricingEngine("black_scholes")
8
9 # Generate portfolio
10 n = 100000
11 spots = np.random.uniform(90, 110, n)
12 strikes = np.full(n, 100.0)
13 vols = np.random.uniform(0.15, 0.25, n)
14 times = np.random.uniform(0.5, 2.0, n)
15
16 # Benchmark
17 start = time.time()
18 prices = engine.calculate_portfolio(spots, strikes, vols, times, 0.05)
19 elapsed = time.time() - start
20
21 print(f"Calculated {n:,} prices in {elapsed*1000:.2f}ms")
22 print(f"Throughput: {n/elapsed:,.0f} prices/second")
23 print(f"Average price: {prices.mean():.2f}")
24
25if __name__ == "__main__":
26 benchmark()
27Real-world benchmark results from our systems:
| Operation | Pure Python | Python+ctypes | Python+PyBind11 | Rust FFI | OCaml FFI | Pure C++ |
|---|---|---|---|---|---|---|
| Single call overhead | 1.0x | 1.2x | 1.1x | 1.05x | 1.1x | 1.0x |
| Array processing (1K) | 100x | 2.5x | 1.2x | 1.05x | 1.1x | 1.0x |
| Array processing (1M) | 1000x | 10x | 1.1x | 1.02x | 1.05x | 1.0x |
| Memory overhead | High | Low | Low | None | Low | None |
1// Rust: Careful with lifetimes across FFI boundary
2pub struct DataWrapper {
3 data: Vec<f64>,
4}
5
6impl DataWrapper {
7 // ❌ WRONG: Returning pointer to data that will be dropped
8 pub fn wrong_get_ptr(&self) -> *const f64 {
9 self.data.as_ptr()
10 }
11
12 // ✅ CORRECT: Pin the data or use Box::leak for stable pointer
13 pub fn correct_get_ptr(&mut self) -> *const f64 {
14 // Convert to Box so it won't move
15 let boxed = self.data.clone().into_boxed_slice();
16 let ptr = boxed.as_ptr();
17 Box::leak(boxed); // Intentional leak; manage lifetime manually
18 ptr
19 }
20
21 // ✅ BETTER: Let caller manage memory
22 pub fn get_data(&self) -> &[f64] {
23 &self.data
24 }
25}
261# Python: Robust error handling with FFI
2import ctypes
3from enum import IntEnum
4
5class ErrorCode(IntEnum):
6 SUCCESS = 0
7 NULL_POINTER = 1
8 INVALID_ARGUMENT = 2
9 ALLOCATION_FAILED = 3
10
11class FFIError(Exception):
12 def __init__(self, code: ErrorCode, message: str):
13 self.code = code
14 super().__init__(f"FFI Error {code.name}: {message}")
15
16# Enhanced wrapper with error handling
17lib.pricing_engine_create.restype = c_void_p
18lib.pricing_engine_create.errcheck = lambda v, *a: (
19 v if v else (_ for _ in ()).throw(
20 FFIError(ErrorCode.ALLOCATION_FAILED, "Failed to create engine")
21 )
22)
231(* OCaml: Thread-safe FFI with mutex *)
2open Ctypes
3open Foreign
4
5module ThreadSafePricer = struct
6 type t = {
7 engine: pricing_engine structure ptr;
8 mutex: Mutex.t;
9 }
10
11 let create model_type =
12 let engine = PricingEngine.create model_type in
13 { engine; mutex = Mutex.create () }
14
15 let calculate t ~spot ~strike ~volatility ~time_to_expiry ~risk_free_rate =
16 Mutex.lock t.mutex;
17 Fun.protect
18 ~finally:(fun () -> Mutex.unlock t.mutex)
19 (fun () ->
20 PricingEngine.calculate t.engine
21 ~spot ~strike ~volatility ~time_to_expiry ~risk_free_rate
22 )
23end
24How we combine all these pieces:
1┌──────────────────────────────────────────────────────┐
2│ Python Layer (Research & Strategy Development) │
3│ - Jupyter notebooks for research │
4│ - NumPy/Pandas for data analysis │
5│ - PyBind11 bindings to C++ pricing library │
6└───────────────┬──────────────────────────────────────┘
7 │ REST API / gRPC
8┌───────────────▼──────────────────────────────────────┐
9│ Rust Layer (Application Services) │
10│ - HTTP server (Actix/Axum) │
11│ - Message queue consumers │
12│ - FFI to OCaml for business logic │
13└───────────────┬──────────────────────────────────────┘
14 │ C ABI
15┌───────────────▼──────────────────────────────────────┐
16│ OCaml Layer (Risk & Order Management) │
17│ - Type-safe business logic │
18│ - Ctypes FFI to C++ libraries │
19└───────────────┬──────────────────────────────────────┘
20 │ C ABI
21┌───────────────▼──────────────────────────────────────┐
22│ C++ Layer (Ultra-Low Latency) │
23│ - Market data parsing │
24│ - Order execution │
25│ - Pricing algorithms │
26└──────────────────────────────────────────────────────┘
27Multi-language systems let you use the right tool for each job. The key is clean interfaces, careful memory management, and understanding the trade-offs. When done well, you get Python's productivity, Rust's safety, OCaml's type system, and C++'s performance—all in one system.
The FFI overhead is negligible when you batch operations and design interfaces carefully. The real cost is complexity, so only cross language boundaries when the benefits are clear.
Building multi-language systems? Contact us to discuss architecture and implementation strategies.
Technical Writer
NordVarg Team is a software engineer at NordVarg specializing in high-performance financial systems and type-safe programming.
Get weekly insights on building high-performance financial systems, latest industry trends, and expert tips delivered straight to your inbox.