Generics not so simple

It appears the idea to replace a concrete type by a generic type parameter is naive…

Here I have to fn which only differ by the type used i32 and bool:


I chose the bool one and replaced all occurrences of bool by a type parameter T.

                                          Option<extern fn(command_handle: i32, err: ErrorCode,
                                                         result: T)>) where T: Sized {
        let (sender, receiver) = channel::<(ErrorCode, T)>();

        lazy_static! {
            static ref CALLBACKS: Mutex<HashMap<i32, Box<FnMut(ErrorCode, T) + Send>>> = Default::default();
        }

        let closure = Box::new(move |err: ErrorCode, val: T| {
            sender.send((err, val)).unwrap();
        });

        extern "C" fn _callback(command_handle: i32, err: ErrorCode, result: T) {
            let mut callbacks = CALLBACKS.lock().unwrap();
            let mut cb = callbacks.remove(&command_handle).unwrap();
            cb(err, result)
        }

        let mut callbacks = CALLBACKS.lock().unwrap();
        let command_handle = (COMMAND_HANDLE_COUNTER.fetch_add(1, Ordering::SeqCst) + 1) as i32;
        callbacks.insert(command_handle, closure);

        (receiver, command_handle, Some(_callback))
    }

Well that is not so simple. Rust complains

error[E0401]: can't use type parameters from outer function
   --> tests/utils/callback.rs:101:75
    |
95  |     pub fn _closure_to_cb_ec_one<T>() -> (Receiver<(ErrorCode, T)>, i32,
    |            --------------------- - type variable from outer function
    |            |
    |            try adding a local type parameter in this method instead
...
101 |             static ref CALLBACKS: Mutex<HashMap<i32, Box<FnMut(ErrorCode, T) + Send>>> = Default::default();
    |                                                                           ^ use of type variable from outer function

error[E0401]: can't use type parameters from outer function
   --> tests/utils/callback.rs:108:78

I found several discussion about this but fail to understand how I can fix this.

How do I do this? Thanks.

Rust generics are not working like “find’n’replace” of template arguments for each possible value. They’re more like a single verison of a function that theoretically has to work for all possible cases.

Also Box<Trait> is Box<dyn Trait> which is only one copy abstracting over types at run time.

Box<dyn FnMut(ErrorCode, T) + Send>>>

Functions callable with different types are technically different — there may be a different way of passing parameters to them. While i32 and bool are not that different, T could be anything, including floats, large structs and arrays, which makes code calling these functions very different.

Box<dyn FnMut(ErrorCode, i32) + Send>>> can hold all kinds of functions and closures, but all are callable in the same way. But Box<dyn FnMut(ErrorCode, T) + Send>>> goes one level of abstraction too far, and wants all kinds of closures and functions callable in all kinds of ways. It’s physically impossible to write that code (sometimes arguments would go to registers, sometimes on stack, and there’s no room to store information which is which).

You can change callback to Box<dyn FnMut(ErrorCode, BoolOrNumber) + Send>>> where BoolOrNumber is an enum and you can require T: Into<BoolOrNumber> bound on the generic function. Or Box<BoolCallbackOrI32Callback + Send>>>.

1 Like

quick experiment the static isn’t constrained to each instance of the generic.

You could try (but not pretty);

lazy_static! {
    static ref CALLBACKS: Mutex<Vec<dyn Any>> = Default::default();
}

Iterate the vec calling downcast to the HashMap, insert one if one does not exist.

Thank you.
Still it seems wired that all the callbacks are more or less the same.
Some have zero, some one, some two … parameters.
It just feels wrong to have them all.

And I actually don’t want this to be a runtime thing that it seems to be.
Maybe I’ll try to stuff them into a macro indy_callback!(), indy_callback!(i32), indy_callback!(bool)
Or should I learn to live with this boilerplate.

Mostly I am doing this to learn Rust.