0

I need to be able to pass a reference as a parameter to a callback function that doesn't have any parameters.

My initial thought is to pass it via a Thread-Local Variable, but I can't figure out the lifetimes needed.

Here is my minimum code example to help it make more sense:

use std::cell::Cell;

thread_local!(
    static CLOSURE: Cell<Option<&'static Fn(i32) -> ()>> = Cell::new(None);
);

fn callback_function() {
    CLOSURE.with(|cell| {
        let closure = cell.get().unwrap();
        (*closure)(5);
    });
}

fn not_my_function() {
    // ...
    callback_function();
    // ...
}

fn main() {
    // This is the closure I want called from the callback function
    let foo = |x| {
        println!("The number is {}", x);
    };

    CLOSURE.with(|cell| {
        cell.set(Some(&foo));
    });

    // I don't own this function, but it will eventually call
    // callback_function() without any parameters
    not_my_function();

    // Erase reference in TLV
    CLOSURE.with(|cell| {
        cell.set(None);
    });
}

Rust playground

Not surprisingly, the compiler is not happy about some of the lifetime problems implied here.

error[E0373]: closure may outlive the current function, but it borrows `foo`, which is owned by the current function
  --> src\main.rs:26:22
   |
26 |         CLOSURE.with(|cell| {
   |                      ^^^^^^ may outlive borrowed value `foo`
27 |             cell.set(Some(&foo));
   |                            --- `foo` is borrowed here
help: to force the closure to take ownership of `foo` (and any other referenced variables), use the `move` keyword
   |
26 |         CLOSURE.with(move |cell| {
   |                      ^^^^^^^^^^^

I'm pretty much at a loss of what-to-do to make it work at this point. I know something that does work is to move ownership of the closure to the TLV by changing it to RefCell<Option<Box<Fn(i32) -> ()>>> and then taking ownership back later on, but is there any way to do it without the overhead of Box by simply using references?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
something_clever
  • 806
  • 1
  • 7
  • 17
  • I suppose that you tried what the error message is suggesting you to do? The most relevant error message will pop up once you do that. – E_net4 Apr 24 '18 at 19:30
  • The problem is that the error message is suggesting to transfer ownership of `foo` to the TLV closure. I will still need access to `foo` later in the function, and even so `foo` doesn't have a fixed size and so I couldn't store it in the TLV without boxing it. – something_clever Apr 24 '18 at 19:32
  • Yes, there are multiple issues resulting from an attempt to (1) share a closure with multiple contexts, and (2) one of them being a global variable expecting a closure living for the static lifetime, which is not the case. – E_net4 Apr 24 '18 at 19:54
  • Do you have any suggestions though for how I *could* do it? Perhaps I need to resolve to just using raw pointers and unsafe code? – something_clever Apr 24 '18 at 19:56
  • For example - Is there perhaps a way to make it so the global variable will accept something that has less than a `'static` lifetime? – something_clever Apr 24 '18 at 19:57
  • I'm not understanding exactly where the line is drawn between your code and the library code. How do you set the callback at all? What prevents you from using a function pointer instead of a closure? – Shepmaster Apr 24 '18 at 19:58
  • I didn't want to document all the ceremony - But earlier on in my program I passed my callback function to some initialization routine. It's enough for illustration purposes though to know that it will be called when I call `not_my_function()`, and that it doesn't allow me to pass any parameters to it. – something_clever Apr 24 '18 at 19:58
  • 1
    But *what types* does it take for the callback? Does it take a closure or does it have to be a function pointer? Is this actually C FFI code? – Shepmaster Apr 24 '18 at 20:01
  • Oh I see - It's a C-style callback to a function that doesn't allow any parameters to be passed in, so I can't do the old "pass in a void*" trick. Basically if I'm passing parameters to this function, it's through global/TLV. – something_clever Apr 24 '18 at 20:03
  • 1
    I believe that the answer to this question can be found in one or more of [How do I convert a Rust closure to a C-style callback?](https://stackoverflow.com/q/32270030/155423); [How do I create a Rust callback function to pass to a FFI function?](https://stackoverflow.com/q/31463426/155423); [How do I pass closures through raw pointers as arguments to C functions?](https://stackoverflow.com/q/38995701/155423) – Shepmaster Apr 24 '18 at 20:04
  • 1
    Specifically, [this answer](https://stackoverflow.com/a/42620494/155423) shows a working pattern. Using a reference cannot be proven safe because it would be a reference to a stack variable and your thread-local variable may be used after that stack has been finished. – Shepmaster Apr 24 '18 at 20:19
  • 1
    @Shepmaster Thank you very much for all the other threads about this. I guess my Google-Fu was weak on this one. So yes - It looks like the long answer is that - since my callback doesn't allow a `void*` to be passed, and I know that my lifetimes are fine but can't prove it to the compiler, I will have to use TLV and raw pointers. – something_clever Apr 24 '18 at 20:58

0 Answers0