Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

C and other languages

The diffsol-c crate provides two higher-level APIs for using Diffsol from other languages with a C-compatible FFI:

  1. A dynamic dispatch API — a Rust API that wraps the core diffsol solver 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.
  2. 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:

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:

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.