This is a really well-written article about a common way to trampoline Rust methods into C embedded RTOS tasks, which often work in exactly this way. I've done just this in the service of getting ChibiOS and uC/OS II running Rust code.
Is this a neat trick or just standard operating procedure for calling C from <your favorite lang>? As it was billed as a trick, I was expecting some sort of runtime code generation to pass the data pointer and some jump instruction to jump to the right spot and unpack the data pointer.<p>Maybe I just overcomplicate things ;-)
Interestingly this is very similar to how I implemented passing closures into JavaScriptCore as hooks for JS class invocations ("function calls"). [0]<p>Essentially it's taking advantage of the fact that closures are static methods with "implicit" data pointers. It should be fairly obvious that this is a massive violation of safety and undefined behavior, and most likely to break when debugging symbols etc. are inserted.<p>The safest way to do this until Rust has figured out a stable-enough-ABI for closure passing would be a thread-local trampoline, I guess. Not very nice..<p>[0] <a href="https://github.com/psychonautwiki/rust-ul/blob/master/src/helpers_internal.rs#L67-L118" rel="nofollow">https://github.com/psychonautwiki/rust-ul/blob/master/src/he...</a>
You can pass closures to C as C functions in TXR Lisp, a language I created.<p>Example:<p><a href="http://rosettacode.org/wiki/Window_creation#Win32.2FWin64" rel="nofollow">http://rosettacode.org/wiki/Window_creation#Win32.2FWin64</a><p>In this program, a translation of Microsoft's "Your First Windows Program" from MSDN, <i>defun</i> is used to define a WindowsProc callback. <i>defun</i> generates a <i>lambda</i> under the hood, which carries a lexical scope.<p>The lambda is passed directly to Win32 as a callback, which is nicely called for repainting the window. (Or at least, things appear that way to the programmer.)<p>Setting this up requires a few steps. We need a target function, of course, which can be any callable object.<p>Then there is this incantation:<p><pre><code> (deffi-cb wndproc-fn LRESULT (HWND UINT LPARAM WPARAM))
</code></pre>
The <i>deffi-cb</i> operator takes a name and some type specifications: return type and parameters. The name is defined as a function; so here we get a function called <i>wndproc-fn</i>. This function is a converter. If we pass it a Lisp function, it gives back a FFI closure object.<p>Then in the program, we instantiate this closure object, and stick it into the WNDPROC structure as required by the Windows API. Here we use the above wndproc-fn converter to obtain WindowProc in the shape of a FFI closure:<p><pre><code> (let* ((hInstance (GetModuleHandle nil))
(wc (new WNDCLASS
lpfnWndProc [wndproc-fn WindowProc]
...
</code></pre>
The lpfnWndProc member of the WNDCLASS FFI structure is defined as having the FFI type <i>closure</i>; that will correspond to a function pointer on the C side. The rest is just Windows:<p><pre><code> (RegisterClass wc)
</code></pre>
register the class, and then CreateWindow with that class by name and so on.
How does this relate to nested functions in C? (And resulting “infectious executable stacks”?)<p><a href="https://nullprogram.com/blog/2019/11/15/" rel="nofollow">https://nullprogram.com/blog/2019/11/15/</a>
Rust is already doomed. The amount of literature being published about either comparisons or compatibility with C is a strong indicator C is here to stay.