Is there a way to generify this?

Playground: Rust Playground

I want to generify FnHolder so it can take any type of FnMut with any singular return type. However, FnHolder::invoke() requires an explicit return type that is implied within the existence of the FnMut. Is there a way to generify this?

use std::pin::Pin;
extern crate crossbeam; // 0.7.2

use crossbeam::atomic::AtomicCell;
use std::sync::Arc;

struct Tmp {
    value: usize
}

struct FnHolder {
    inner: Arc<AtomicCell<Box<dyn FnMut(usize) -> usize>>>
}

unsafe impl Send for FnHolder {}
unsafe impl Sync for FnHolder {}

impl Clone for FnHolder {
    fn clone(&self) -> Self {
        Self {inner: self.inner.clone()}
    }
}

impl FnHolder {
    pub fn new(fx: Box<dyn FnMut(usize) -> usize>) -> Self {
        Self {inner: Arc::new(AtomicCell::new(fx))}
    }
    
    pub fn invoke(&self, input: usize) -> usize {
        unsafe { (&mut *self.inner.as_ptr())(input) }
    }
}

impl Tmp {
    // Contract: follow the contract of Pin, and, keep self alive at least as long as the boxed closure below
    fn create_fn(mut self: Pin<&mut Self>) -> Box<dyn FnMut(usize) -> usize> {
    let self_ptr = &mut *self as *mut Self; // We have to ensure that self lives-on as long as this function gets called
        Box::new(
            move |val: usize| unsafe {
                let this = &mut *self_ptr;
                this.value += val;
                this.value
            }
        )
    }
}

fn main() {
    let mut tmp = Box::pin(Tmp { value: 0 });
    
    let fn_holder = FnHolder::new(tmp.as_mut().create_fn());
    //std::mem::drop(tmp); Doing this violates the contract
    for idx in 0.. 1000 {
    let fn_holder = fn_holder.clone();
        std::thread::spawn(move || {
            println!("[{}] {}", idx, fn_holder.invoke(10)); 
        }).join();
    }

    println!("{}", fn_holder.invoke(10));
}

Something like this?

Also AtomicCell<T> requires T to be Send in order to be Send + Sync which isn't the case here because of the raw pointer. Raw pointers are not defined as Send nor Sync. It's the responsibility of the user to implement them if they need to and it's safe. With your current code you can violate Send and Sync guaranties. For example:

let rc = std::rc::Rc::new(0);
Box::new(
    move |val: usize| unsafe {
        let this = &mut *self_ptr;
        &rc;
        this.value += val;
        this.value
    }
)

And now I have a Rc going across thread boundaries.
I ignored this Send + Sync issue in the generic code but it's still there.

1 Like

Yeah, that worked perfectly. I actually want different input and outputs, but using your thinking, all I have to do is add an additional type param for the output and it compiles.

Too bad negative trait bounds aren't supported, otherwise fixing this would be really simple.

Luckily, this is for a lower-level portion of my code that is far away from the API layer. Thus, I can be as dark as I wish

But yeah, no sending Rc's across type of nonsense :joy:

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.