How to convert a Rust closure to a C-style callback?

The outer closure fed to set_write_callback can keep the loose bounds of "just" Send + 'static + FnMut(...), and then you do let closure = mutexed_closure(closure); inside the function to let Mutex add the thread-safety and panic-safety guarantees.

Then, the only annoying thing remaining is to be able to define the helper extern "C" function with what now are unnameable types.

macro_rules! trait_alias {(
    trait $Trait:ident $(<$T:ident>)? = $($super:tt)*
) => (
    trait $Trait $(<$T>)? : $($super)*
    {}
    impl<__ : ?Sized, $($T)?> $Trait $(<$T>)? for __
    where Self : $($super)*
    {}
)}

trait_alias! {
    trait ThreadSafe = Send + Sync
}
trait_alias! {
    trait MyClosureMut<T> = FnMut(String, T) -> Result<(), String>
}
trait_alias! {
    trait MyClosure<T> = Fn(String, T) -> Result<(), String>
}
use ::std::panic::RefUnwindSafe as PanicSafe;

// ... 

pub
fn set_write_callback<T> (
    self: &'_ mut Self,
    closure: impl 'static + Send + MyClosureMut<T>,
) -> Result<(), String>
where
    T : ...
{
    unsafe extern "C"
    fn std_wr_handler<Closure, T> (args: *mut CallbackArgs)
      -> Status::T
    where
        Closure : 'static + ThreadSafe + PanicSafe + MyClosure<T>,
        T : ...
    {
        ...
    }

    // make the closure become `ThreadSafe` and `PanicSafe`:
    let closure = mutexed_closure(closure);
    let std_wr_handler = {
        // The only issue is that now we cannot name the type of `closure`,
        // so let's use a helper trait:
        trait Helper<T> : 'static + ThreadSafe + PanicSafe + MyClosure<T> {
            fn get_std_wr_handler (&self)
              -> unsafe extern "C" fn(_: *mut CallbackArgs) -> Status::T
            ;
        }
        impl<T, Closure> Helper<T> for Closure
        where
            Closure : 'static + ThreadSafe + PanicSafe + MyClosure<T>
        {
            fn get_std_wr_handler (&self)
              -> unsafe extern "C" fn(_: *mut CallbackArgs) -> Status::T
            {
                std_wr_handler::<Closure, T>
            }
        }
        closure.get_std_wr_handler()
    };

    match self.callback_handle {
        | Some(ref mut callback_handle) => {
            IEC_attr_set_cb(
                callback_handle,
                std_wr_handler as *mut _,
                ptr::null_mut(),
                &callback as *const _ as *mut c_void,
            )
        },
        | None => Err("Cannot set write callback".to_string()),
    }
}

Another option is to keep the function as the one from the previous post, and then offer the convenience function as follows:

fn set_write_callback_mutexed<T> (
    self: &'_ mut Self,
    callback: impl 'static + Send + FnMut(...) -> _,
) -> Result<(), String>
where
    T : ...
{
    self.set_write_callback(mutexed_closure(callback))
}
1 Like

I cant' compile your example. This is hardcore.

error[E0599]: no method named `get_std_wr_handler` found for opaque type `impl MyClosureMut<T>+std::marker::Send+std::marker::Sync` in the current scope
   --> src/lib.rs:175:41
    |
175 |         let std_wr_handler = mx_closure.get_std_wr_handler();
    |                                         ^^^^^^^^^^^^^^^^^^ method not found in `impl MyClosureMut<T>+std::marker::Send+std::marker::Sync`
    |
    = note: `mx_closure` is a function, perhaps you wish to call it
    = note: the method `get_std_wr_handler` exists but the following trait bounds were not satisfied:
            `impl MyClosureMut<T>+std::marker::Send+std::marker::Sync: MyClosure<_>`
            which is required by `impl MyClosureMut<T>+std::marker::Send+std::marker::Sync: Helper<_>`

trait_alias! { trait ThreadSafe = Send + Sync }
trait_alias! { trait MyClosureMut<T> = FnMut(String, T) -> Result<(), String> }
trait_alias! { trait MyClosure<T> = Fn(String, T) -> Result<(), String> }

trait Helper<T> : 'static + ThreadSafe + PanicSafe + MyClosure<T> {
    fn get_std_wr_handler (&self) -> unsafe extern "C" fn(_: *mut CallbackArgs) -> IEC_STATUS_T;
}

impl MyStruct
{
    pub fn mutexed_closure<String, T> (f: impl MyClosureMut<T> + 'static + Send) ->
    impl MyClosureMut<T> + 'static + Send + Sync //+ panic::UnwindSafe + panic::RefUnwindSafe
    {
        let mut f = Mutex::new(f);
        move |a, b| {
            //f.lock().unwrap()(a, b)
            let func = f.get_mut().unwrap();
            func(a,b)
        }
    }

    pub fn set_write_callback<T> (self: &'_ mut Self, closure: impl 'static + Send + MyClosureMut<T>, ) -> Result<(), String>
        where T: std::fmt::Display + Default
    {
        unsafe extern "C" fn std_wr_handler<Closure, T> (args: *mut CallbackArgs) -> IEC_STATUS_T
            where Closure : 'static + ThreadSafe + PanicSafe + MyClosure<T>, T: std::fmt::Display + Default
        {
            // ...
            return IEC_STATUS_T::IEC_OK;
        }

        impl<Closure, T> Helper<T> for Closure
            where Closure : 'static + ThreadSafe + PanicSafe + MyClosure<T>, T: std::fmt::Display + Default
        {
            fn get_std_wr_handler (&self) -> unsafe extern "C" fn(_: *mut CallbackArgs) -> IEC_STATUS_T
            {
                std_wr_handler::<Closure, T>
            }
        }

        // make the closure become `ThreadSafe` and `PanicSafe`:
        let mx_closure = IecVariable::mutexed_closure(closure);

        let std_wr_handler = mx_closure.get_std_wr_handler();

        //let handler = std_wr_handler as *mut c_void;

        return Ok(()); //Temp. for test

        //match self.callback_handle {| Some(ref mut callback_handle) => {
        //IEC_attr_set_cb(callback_handle, handler,ptr::null_mut(), &closure as *const _ as *mut c_void)}, | None => Err("Cannot set write callback".to_string()),}
    }
  }

@Yandros, can You help me?

Hello,

I am currently solving the same problem, I have found a way on linux to pass closures as parameter-less function pointers to C code, it utilizes the callback library from libffcall1-dev (Ubuntu package).

Example at Rust Playground , it should be possible to extend it for your use case of functions with one parameter, the function pointer returned from Callback::callback() can be used in C APIs.

I would welcome any comments/suggestions.

1 Like

Yes, sorry, I've been quite busy as of late.

My suggested code had some issues regarding limitations of Rust I hadn't taken into consideration:

  • regarding mutexed_closure, Rust automatically &-derefs the f.lock().unwrap() local only to realize it should have &mut-deref-ed it instead, and errors. This can be solved by explicitly using a &mut deref:

      let f = Mutex::new(f);
      move |...| {
    -     f.lock().unwrap()(...)
    +     (&mut *f.lock().unwrap())(...)
      }
    
  • Regarding the .get_std_wr_handler() call, given that the closure implements Closure<T>, it no longer is 'static unless T is (this is a very annoying limitation of the trait system, since here T is a parameter to one of the methods of the trait, so the implementor should be contravariant in T, meaning there should be no need for T to be 'static for the implementor to be so). Well, let's not go down the road circumventing that limitation (would require manually unrolling trait objects and their vtables, using unsafe), and for the time being, add a T : 'static bound.

      where
    -     T : ...
    +     T : 'static + ...
    
  • Playground


That being said, I am not very pleased with the aesthetics / lack of simplicity of the trait approach used to get a named type parmeter.

I think that providing two methods is the cleanest and most flexible approach:

  • one method (set_write_callback) that takes a MyClosure<T> that must be 'static + ThreadSafe + PanicSafe, with nothing in the implementation changed (w.r.t. my post, the one before you mentioned that Fn was too restrictive),

  • and a second closure that loosens the bounds to 'static + Send + MyClosureMut<T>, and that mutexes the closure so that it can be fed to set_write_callback:

impl Struct {
+   pub
+   fn set_write_callback_mutexed<T> (
+       self: &'_ mut Self,
+       closure: impl 'static + Send + FnMut(String, T) -> Result<(), String>
+   ) -> Result<(), String>
+   where
+       T : 'static + Default + panic::UnwindSafe,
+   {
+       self.set_write_callback(mutexed_closure(closure))
+   }

              /* Rest as usual, unchanged: */
    pub
    fn set_write_callback<T, Closure> (
        self: &'_ mut Self,
        callback: Closure,
    ) -> Result<(), String>
    where
        Closure : Fn(String, T) -> Result<(), String>, // closure is callable by `&`
        Closure : 'static, // closure owns its environment / does not borrow any locals
        Closure : Send + Sync, // closure is thread safe
        Closure : panic::RefUnwindSafe, // closure panicking does not let stuff in a badly corrupted state
        T : 'static + Default + panic::UnwindSafe, // value fed to the closure is also unwind safe
    {
        unsafe extern "C"
        fn std_wr_handler<Closure, T> (args: *mut CallbackArgs)
          -> Status::T
        where
            Closure : Fn(String, T) -> Result<(), String>, // closure is callable by `&`
            Closure : 'static, // closure owns its environment / does not borrow any locals
        Closure : Send + Sync, // closure is thread safe
            Closure : panic::RefUnwindSafe, // closure panicking does not let stuff in a badly corrupted state
            T : Default + panic::UnwindSafe, // value fed to the closure is also unwind safe
        {
1 Like

A similar API is libffi, which has high-level Rust bindings, too. I'd recommend using that since libffi seems to be more actively maintained.

I've had portability issues with libffi to Windows in the past (but I'm guessing libffcall also does), though it seems that support has been added to libffi for MSVC recently, so you may be able to get it to work.

1 Like

Thx for reply.

fn mutexed_closure<T> (
    f: impl FnMut(String, T) -> Result<(), String>
          + 'static
          + Send
    ,
) -> impl Fn(String, T) -> Result<(), String>
        + 'static
        + ThreadSafe
        + panic::UnwindSafe + panic::RefUnwindSafe
{
    let f = ::std::sync::Mutex::new(f);
    move |string, t| {
        (&mut *f.lock().unwrap())(string, t)
    }
}


//...
  let result = panic::catch_unwind(|| {
                unsafe_closure("test".to_string(), T::default())
            });

Is the Fn (...) -> FnMut () conversion to use catch_unwind () elegant, correct?
The documentation says that catch_unwind () can only be used for Fn (...).
Is it dangerous to "hack" it?

Regarding Fn() vs. FnMut(), panic::catch_unwind can be used for any FnOnce() -> _, which both FnMut() implementors and Fn() implementors implement.

  • "Reminder": FnOnce() means callable by value (self), FnMut() means callable by exclusive / unique (hence why it is incompatible with multi-threading) reference (&mut self), and Fn() means callable by shared reference (&self). And given self, you can always borrow it exclusively (since you own it) to obtain a &mut self: thus, F : FnMut() ⇒ F : FnOnce (Proof). Similarly, reborrowing &mut self as &self leads to F : Fn() ⇒ F : FnMut().

    All this is written, as a shorthand, as Fn() : FnMut() : FnOnce().

Regarding catch_unwind, using Mutex is the idiomatic thing to do, since in case of a panic, the Mutex enters a poisoned state whereby any further .locking() from that thread or another will keep causing panic!s, thus avoiding to observe the buggy state That's how Mutex is able to provide [Ref]UnwindSafe to what it wraps, not matter what that is :slightly_smiling_face:.

1 Like

I have a problem with panic::catc_unwind() in my code.

let catch_result = panic::catch_unwind(|| {
    closure_cb(name, var_ref.name.clone(), val)
});

Error:

error[E0277]: the type `(dyn MyClosure<T, Output = std::result::Result<(), std::string::String>> + 'static)` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
   --> src/lib.rs:254:32
    |
254 |             let catch_result = panic::catch_unwind(|| {
    |                                ^^^^^^^^^^^^^^^^^^^ `(dyn MyClosure<T, Output = std::result::Result<(), std::string::String>> + 'static)` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
    | 
   ::: /home/mhanusek/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd/panic.rs:393:40
    |
393 | pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
    |                                        ---------- required by this bound in `std::panic::catch_unwind`
    |
    = help: within `&mut (dyn MyClosure<T, Output = std::result::Result<(), std::string::String>> + 'static)`, the trait `std::panic::RefUnwindSafe` is not implemented for `(dyn MyClosure<T, Output = std::result::Result<(), std::string::String>> + 'static)`
    = note: required because it appears within the type `&mut (dyn MyClosure<T, Output = std::result::Result<(), std::string::String>> + 'static)`
    = note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&&mut (dyn MyClosure<T, Output = std::result::Result<(), std::string::String>> + 'static)`
    = note: required because it appears within the type `[closure@src/lib.rs:254:52: 256:14 closure_cb:&&mut (dyn MyClosure<T, Output = std::result::Result<(), std::string::String>> + 'static), name:std::string::String, var_ref:&&IecVariable<T>, val:T]`

error: aborting due to previous error; 1 warning emitted

Does {closure_cb}(name, ...) help?

  let catch_result = panic::catch_unwind(|| {
-     closure_cb(name, var_ref.name.clone(), val)
+     {closure_cb}(name, var_ref.name.clone(), val)
  });

trait_alias! { trait MyClosure<T> = Fn(String, String, T) -> Result<(), String> + std::panic::UnwindSafe + std::panic::RefUnwindSafe}

I'm add std::panic::UnwindSafe + std::panic::RefUnwindSafe. This solved the problem.

1 Like

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.