Rust closure as C-compatible function and env pointers pair

For a little project of mine I need some numerical integration routine. Rust does not have native math library yet, so I decided to bind to some already written C library, namely to GSL.

GSL integration facilities accept a pointer to a struct gsl_function_struct, defined like this:

struct gsl_function_struct 
{
  double (* function) (double x, void * params);
  void * params;
};

That is, it is a pointer to a function and an additional parameter to that function.

This is obviously a closure-like structure: a function and an arbitrary environment, so supposedly high-level closures should fit into such structure nicely.

Is it possible to create such a structure out of Rust closure? As far as I know, Rust closures are exactly a function+env pointers pair. Can I convert them to GSL representation then? I tried to dig through rustc code, but to no avail, understanding it is well above my abilities :)

7 comments · 73% upvoted · other discussions

comments

dbaupp

Yes, a Rust closure is essentially the same thing. Theoretically, you can transmute a |...| to a struct/tuple of function pointer & environment pointer just like gsl_function_struct (and vice versa) to construct & deconstruct closures manually. (std provides the raw module to make manipulating low-level representations like this safer, e.g. Closure for |...|).

However, the calling convention of a Rust function (including the underlying function of a |...|) is not the same as that of a C function, in general. So converting a Rust closure to a GSL closure is liable to cause weird platform-specific corruption, due to arguments being passed from C to Rust differently to what Rust is expecting (and vice versa).

dpx-infinity [OP]

Great! I completely forgot about std::raw module. That's exactly what I need. I believe can create a single double (* function) (double x, void * params) function and pass the closure data to it, so it would call the closure normally. Thanks!

dbaupp

If you mean something like

struct gsl_function_struct {
    function: extern "C" fn(x: f64, params: *mut c_void),
    params: *mut c_void
}

extern "C" call_rust_closure(x: f64, params: *mut c_void) -> f64 {
    let f = params as *mut |f64| -> f64
    unsafe {(*params)(x)}
}

fn closure_to_gsl_function(f: &mut |f64| -> f64) -> gsl_function_struct {
    gsl_function_struct {
          function: call_rust_closure,
          params: f as *mut _ as *mut c_void
    }
}

some_gsl_integration_function(closure_to_gsl_function(&mut |x| x + 1.0));

then yes, I guess it would work.

Anything that tries to pass the Rust-closure's function pointer to GSL as the gsl_function_structs function pointer hits the calling convention issues I mentioned above (that's what the extern "C" function is for, interfacing between the world of C functions and the world of Rust functions). I urge you to avoid passing a destructured Rust closure directly to C... basically: forget I mentioned std::raw at all.

(If you do take the route above, I would strongly recommend defining a wrapper struct for gsl_function_struct that uses the ContravariantLifetime marker to connect the input closure with the output GSL closure i.e.

struct GslRustClosure<'a> {
    lifetime: ContravariantLifetime<'a>,
    function: gsl_function_struct 
}

fn closure_to_gsl_function<'a>(f: &'a mut |f64| -> f64) -> GslRustClosure<'a> { ... }

.)

dpx-infinity [OP]

Yeah, I did something like your example, but I've used std::raw::Closure as an intermediate representation, so I didn't have to take a closure by reference. Now when I see what you have written I'm not sure if I did it right, but it does work and it does produce correct results.

dbaupp

You still don't need to handle raw at all to avoid taking the closure by reference; just do the same & thing you've done to a raw::Closure directly to the original |f64| -> f64.

dpx-infinity [OP]

Thanks, it did work, though in integrand I had to use &mut |f64| -> f64 instead of raw |f64| -> f64|. It did make code more clear anyway.

dpx-infinity [OP]

Indeed it does work :) it was easier than I thought. If someone is interested, I've got something like this:

pub struct GslFunctionStruct {
    pub function: extern fn(x: c_double, params: *c_void) -> c_double,
    pub params: *c_void
}

#[link(name = "gsl")]
extern {
    pub fn gsl_integration_qng(f: *GslFunctionStruct, a: c_double, b: c_double, 
                               epsabs: c_double, epsrel: c_double, 
                               result: *mut c_double, abserr: *mut c_double,
                               neval: *mut size_t);
}

// ...

extern fn integrand(x: libc::c_double, arg: *libc::c_void) -> libc::c_double {
    // cast to closure back from *void
    let closure = arg as *raw::Closure;
    let f: |f64| -> f64 = unsafe { cast::transmute(*closure) };
    f(x as f64) as libc::c_double
}

// ...

pub fn integrate_qng(&self, f: |f64| -> f64) -> IntegrationResult {
    // cast the closure to Closure and then a pointer to it to *void
    let closure: raw::Closure = unsafe { cast::transmute(f) };
    let fstruct = ext::GslFunctionStruct {
        function: integrand,
        params: unsafe { cast::transmute(&closure) }
    };
    let (a, b) = self.range;

    let mut result: libc::c_double = 0.0;
    let mut abs_err: libc::c_double = 0.0;
    let mut n_eval: libc::size_t = 0;
    unsafe {
        ext::gsl_integration_qng(&fstruct, a as libc::c_double, b as libc::c_double,
                                 self.eps_abs as libc::c_double, self.eps_rel as libc::c_double,
                                 &mut result, &mut abs_err, &mut n_eval);
    }
    IntegrationResult {
        result: result as f64,
        abs_error: abs_err as f64,
        number_of_evaluations: n_eval as uint
    }
}