C and other languages
The diffsol-c crate provides two
higher-level APIs for using Diffsol from other languages with a
C-compatible FFI:
- A dynamic dispatch API — a Rust API that wraps the core
diffsolsolver in runtime-dispatched types, allowing you to choose the matrix backend, scalar type, linear solver, ODE solver method, and JIT backend at runtime rather than at compile time. - A C FFI API —
extern "C"functions that expose the dynamic dispatch API as a C-compatible interface, enabling direct use from C code or any language with C bindings.
Dynamic Dispatch API
The core diffsol crate requires you to specify the matrix type, code
generation backend, ODE equation type, linear solver, and solver method all as
generic type parameters at compile time. The dynamic dispatch API replaces
these generic parameters with runtime enums, storing the concrete
implementations behind trait objects.
Key types
The central type is the
OdeWrapper,
which wraps the solver state in an Arc<Mutex<...>> for safe sharing across
threads and FFI boundaries. It provides methods to create, configure, solve,
and serialize the solver.
Configuration is done via runtime enum types:
- OdeSolverType: the ODE integration method
- MatrixType: the linear algebra backend
- ScalarType: the floating-point precision for the solver
- LinearSolverType: the linear solver for implicit methods
- JitBackendType: the JIT compilation backend for DiffSL code
Solution results are returned as a SolutionWrapper, from which you can extract the time points, state values, and sensitivities as HostArray objects — read-only views of Rust-allocated data that can be accessed without copying.
Example: Logistic Equation
Below is a complete example of using the dynamic dispatch API to solve the
logistic ODE \(dy/dt = r \cdot y \cdot (1 - y)\) from the Rust side. The
solver is configured entirely at runtime using the OdeWrapper API.
The DiffSL code for the logistic equation:
let code = r#"
in_i { r = 2.0 }
u_i { y = 0.1 }
dudt_i { dydt = 0 }
F_i { (r * y) * (1 - y) }
out_i { y }
"#;
Creating the OdeWrapper with the Cranelift JIT backend, f64 precision, and the BDF solver:
let ode = OdeWrapper::new_jit(
code,
JitBackendType::Cranelift,
ScalarType::F64,
MatrixType::NalgebraDense,
LinearSolverType::Default,
OdeSolverType::Bdf,
)
.unwrap();
Configuring the solver tolerances:
ode.set_rtol(1e-6).unwrap();
ode.set_atol(1e-8).unwrap();
Solving on a fixed time grid:
let t_eval = vec![0.0, 0.25, 0.5, 0.75, 1.0];
let params = vec![2.0f64];
let solution = ode
.solve_dense(params.to_host_array(), t_eval.to_host_array())
.unwrap();
Extracting the solution data from the SolutionWrapper:
let ys = Vec::<Vec<f64>>::from_host_array(solution.get_ys().unwrap()).unwrap();
let ts = Vec::<f64>::from_host_array(solution.get_ts().unwrap()).unwrap();
See the full example at examples/diffsol-c-logistic.
C FFI API
The C FFI API exposes the dynamic dispatch API as extern "C" functions,
making it callable from C or any language with C interop. The functions are
organized into modules suffixed _c in the crate documentation:
- ode_c —
create, configure, solve, and destroy
OdeWrapperhandles. - solution_wrapper_c
— extract solution data and destroy
SolutionWrapperhandles. - ode_options_c and initial_condition_options_c — get/set solver options.
- matrix_type_c, scalar_type_c, linear_solver_type_c, ode_solver_type_c, jit_c — query and convert enum values.
- host_array_c
— inspect and free
HostArraydata. - error_c — retrieve and clear thread-local error messages.
- string_c — allocate and free Rust-owned strings from C.
All C FFI functions follow a common pattern:
- Return
i32(0 = success, negative = error). - Store error details in a thread-local variable, retrievable via functions in
error_c. - Use raw pointers for ownership transfer — the caller allocates and frees via dedicated functions.