C callback into Rust multiple params c_void

I've been ready most examples I can find on this topic but it's still unclear to me how I can massage a rust closure (or any function really) into this type

Here is the type generate by bindgen

pub type GxB_binary_function = ::std::option::Option<
    unsafe extern "C" fn(
        arg1: *mut ::std::os::raw::c_void,
        arg2: *const ::std::os::raw::c_void,
        arg3: *const ::std::os::raw::c_void,
    ),
>;

this is how the C header looks like

typedef void (*GxB_binary_function) (void *, const void *, const void *) ;

#undef GrB_BinaryOp_new

GB_PUBLIC
GrB_Info GrB_BinaryOp_new
(
    GrB_BinaryOp *binaryop,         // handle for the new binary operator
    GxB_binary_function function,   // pointer to the binary function
    GrB_Type ztype,                 // type of output z
    GrB_Type xtype,                 // type of input x
    GrB_Type ytype                  // type of input y
) ;

the Rust closure I would like to massage into this is

    fn new<F>(f: &mut F) -> GrB_BinaryOp
    where 
        F: FnMut(&mut Z, X, Y) -> (),

I do not hold a mutable reference to C it is owned by the C lib I'm trying to call

I tried using ffi_helpers::split_colsure but i'm getting the wrong type

incorrect number of function parameters
expected fn pointer unsafe extern \"C\" fn(*mut std::ffi::c_void, *const std::ffi::c_void, *const std::ffi::c_void)
found fn pointer unsafe extern \"C\" fn(*mut std::ffi::c_void, &mut Z, X, Y)"

A Rust closure can have data stored as part of it, besides just the function pointer, and this data needs to be passed as an argument to the function in some way. This data is the c_void pointer that split_closure added in front of your Z, X and Y.

Which of your three arguments holds this data, and in what manner?

The thing is the closure passed down into C should only write to Z which is mutable.
Normally closures capture an environment but in this case they really should NOT. They should however be able to write the output of the computation to Z

So a closure that I would need to pass down say for complex addition

        let closure = |_z : &mut (f64, f64), x:&(f64, f64), y: &(f64, f64)| { 
            let (rx, ix) = x;
            let (ry, iy) = y;
            _z.0 = rx + ix;
            _z.1 = ry + iy;
        };

So I'd like to pass this closure to GrB_BinaryOp_new as GxB_binary_function.

I'm not 100% what you mean by

Which of your three arguments holds this data, and in what manner?

The C function is called like this
https://github.com/GraphBLAS/LAGraph/blob/master/Source/Algorithm/LAGraph_BF_full.c#L226

This is not (easily) possible given the signature of the C API: it's a callback that seems to be expecting exactly two especially-crafted inputs, and emit its output to a third similarly especially crafted inputs.
That is, there is no void * data "free" parameter on that callback. So you will only be able to use stateless callbacks, i.e. functions.

But then we have the issue of the expected ABI, and the current trait system does not allow to be generic over function(item)s (we would need a trait ItemFn<Args> : Fn<Args> + Copy + Send = Sync + 'static { const IT: Self; }).

So given the generics / type limitation, combined with the C API failure of not supporting even a simple runtime pointer for any extra metadata, we will have to resort to macros, as a poor man's replacement of generics:

fn main ()
{
    let (add, z_p, x_p, y_p) = rawify_function!(
        |z: &mut i32, &x: &i32, &y: &i32| {
            *z = x + y;
        },
        0, 42, 27,
    );
    {
        // Check that the macro has yielded elements
        // with the right signature
        use ::std::os::raw::c_void;
        let _: unsafe extern "C" fn(
            *mut c_void,
            *const c_void,
            *const c_void,
        ) = add;
        let _: *mut c_void = z_p;
        let _: *const c_void = x_p;
        let _: *const c_void = y_p;
    }
    unsafe {
        add(z_p, x_p, y_p);
        let (x, y, z) = (
            Box::from_raw(x_p.cast::<i32>()),
            Box::from_raw(y_p.cast::<i32>()),
            Box::from_raw(z_p.cast::<i32>()),
        );
        dbg!(x, y, z);
    }
}

Implementation:

macro_rules! rawify_function {(
    $function:expr,
    $z:expr,
    $x:expr,
    $y:expr $(,)?
) => ({
    let (z, x, y) = ($z, $x, $y);
    #[allow(unreachable_code)] {
        if false {
            loop {}
            let f: fn(&mut _, &_, &_) = $function;
            f(&mut {z}, &x, &y);
        };
    }
    {
        use ::std::{
            boxed::Box,
            eprintln,
            os::raw::c_void,
            process,
        };
        #[allow(nonstandard_style)]
        unsafe extern "C"
        fn GxB_binary_function (
            arg1: *mut c_void,
            arg2: *const c_void,
            arg3: *const c_void,
        )
        {
            ::scopeguard::defer_on_unwind!({
                eprintln!("\
                    Caught an unwind across an `extern \"C\"` function \
                    boundary, which is Undefined Behavior.\n\
                    Aborting for soundness.\
                ");
                process::abort();
            });
            $function(
                &mut* arg1.cast(),
                &* arg2.cast(),
                &* arg3.cast(),
            )
        }
        (
            GxB_binary_function,
            Box::into_raw(Box::new(z)).cast::<c_void>(),
            Box::into_raw(Box::new(x)).cast::<c_void>(),
            Box::into_raw(Box::new(y)).cast::<c_void>(),
        )
    }
})}

Playground


But more generally, we will need more specific info about your API and examples of right usage from C before we can give Rust equivalents :wink:

OK, by the looks of things maybe closures is not the way to go, my second idea for the RUST API was a BinOp trait can this be coerced into the C call?

I have no need to interact with the world outside this function, it's just a matter of using X and Y to compute Z.

trait BinOp<Z, X, Y> {
    fn call(z: &mut Z, x:&X, y: &Y) -> ();
}

But first some examples from C
https://github.com/GraphBLAS/LAGraph/blob/master/Source/Algorithm/LAGraph_BF_full.c

// a C function
void BF_PLUSrhs
(
    BF_Tuple3_struct *z,
    const BF_Tuple3_struct *x,
    const BF_Tuple3_struct *y
)
{
    z->w = x->w + y->w;
    z->h = x->h + y->h;
    if (x->pi != UINT64_MAX && y->pi != 0)
    {
        z->pi = y->pi;
    }
    else
    {
        z->pi = x->pi;
    }
}
GrB_Type BF_Tuple3;
// more irrelevant stuff to init the custom type

typedef void (*LAGraph_binary_function) (void *, const void *, const void *) ;

// lift it into a binaryOp
GrB_BinaryOp BF_PLUSrhs_Tuple3;
LAGRAPH_OK (GrB_BinaryOp_new(&BF_PLUSrhs_Tuple3,
    (LAGraph_binary_function)(&BF_PLUSrhs),
    BF_Tuple3, BF_Tuple3, BF_Tuple3));

In the end I abandoned the usage of Fn trait and used a simple trait to describe what I wanted

pub trait BinOp {
    type X;
    type Y;
    fn op(&mut self, x: &Self::X, y: &Self::Y) -> ();
}

I pass this down like this

impl<A, B, C> BinaryOp<A, B, C>
where
    C: BinOp<X = A, Y = B>,
{
    #[no_mangle]
    extern "C" fn unsafe_call(
        arg1: *mut ::std::os::raw::c_void,
        arg2: *const ::std::os::raw::c_void,
        arg3: *const ::std::os::raw::c_void,
    ) -> () {
        let z: &mut C = unsafe { &mut *(arg1 as *mut C) };
        let x: &A = unsafe { &*(arg2 as *const A) };
        let y: &B = unsafe { &*(arg3 as *const B) };
        z.op(x, y) // cast cast cast then apply
    }

    pub fn new() -> Self
    where
        A: TypeEncoder,
        B: TypeEncoder,
        C: TypeEncoder,
    {
        let _ = *GRB;
        let grb_op: GrB_BinaryOp = grb_call(|OP: &mut MaybeUninit<GrB_BinaryOp>| unsafe {
            GrB_BinaryOp_new(
                OP.as_mut_ptr(),
                Some(Self::unsafe_call), // wrap and pass it down
                C::blas_type().tpe,
                A::blas_type().tpe,
                B::blas_type().tpe,
            )
        });
        BinaryOp {
            op: grb_op,
            _a: PhantomData,
            _b: PhantomData,
            _c: PhantomData,
        }
    }
}

If you are satisfied with that solution, then since BinOp<X = A, Y = B> is the same API as FnMut(&A, &B), you should be able to write:

impl<A, B, C> BinaryOp<A, B, C>
where
-   C: BinOp<X = A, Y = B>,
+   C: FnMut(&A, &B),
{
    #[no_mangle]
    extern "C" fn unsafe_call(
        arg1: *mut ::std::os::raw::c_void,
        arg2: *const ::std::os::raw::c_void,
        arg3: *const ::std::os::raw::c_void,
    ) -> () {
        let z: &mut C = unsafe { &mut *(arg1 as *mut C) };
        let x: &A = unsafe { &*(arg2 as *const A) };
        let y: &B = unsafe { &*(arg3 as *const B) };
-       z.op(x, y) // cast cast cast then apply
+       z(x, y) // cast cast cast then apply
    }

    pub fn new() -> Self
    where
        A: TypeEncoder,
        B: TypeEncoder,
        C: TypeEncoder,
    {
        let _ = *GRB;
        let grb_op: GrB_BinaryOp = grb_call(|OP: &mut MaybeUninit<GrB_BinaryOp>| unsafe {
            GrB_BinaryOp_new(
                OP.as_mut_ptr(),
                Some(Self::unsafe_call), // wrap and pass it down
                C::blas_type().tpe,
                A::blas_type().tpe,
                B::blas_type().tpe,
            )
        });
        BinaryOp {
            op: grb_op,
            _a: PhantomData,
            _b: PhantomData,
            _c: PhantomData,
        }
    }
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.