Maybe I just overcomplicate things ;-)
Fun fact: you can't safely do this with ctypes. Since it is called as pure Python, it cannot do watertight Python exception handling in a callback context (because even if you have a try/except block, an exception can always happen right before or after it), and ctypes provides no usable internal way of doing it - it just eats exceptions inside callbacks. This is what motivated me to rewrite Ceph's librbd bindings from ctypes to Cython.
The "neat" factor comes from how little type wrangling and unsafe code is needed.
(Also hi, go contribute to Nixpkgs again!)
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.
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..
[0] https://github.com/psychonautwiki/rust-ul/blob/master/src/he...
For callbacks the overhead likely isn't significant.
Example:
http://rosettacode.org/wiki/Window_creation#Win32.2FWin64
In this program, a translation of Microsoft's "Your First Windows Program" from MSDN, defun is used to define a WindowsProc callback. defun generates a lambda under the hood, which carries a lexical scope.
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.)
Setting this up requires a few steps. We need a target function, of course, which can be any callable object.
Then there is this incantation:
(deffi-cb wndproc-fn LRESULT (HWND UINT LPARAM WPARAM))
The deffi-cb 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 wndproc-fn. This function is a converter. If we pass it a Lisp function, it gives back a FFI closure object.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:
(let* ((hInstance (GetModuleHandle nil))
(wc (new WNDCLASS
lpfnWndProc [wndproc-fn WindowProc]
...
The lpfnWndProc member of the WNDCLASS FFI structure is defined as having the FFI type closure; that will correspond to a function pointer on the C side. The rest is just Windows: (RegisterClass wc)
register the class, and then CreateWindow with that class by name and so on.http://www.kylheku.com/cgit/txr/tree/tests/017/qsort.tl
It's done in two ways, as UTF-8 char * strings and as wchar_t * strings.
What's used as the callback is the function cmp-str which is in TXR Lisp's standard library. A lambda expression could be used instead.
Also tested is the perpetration of a non-local control transfer out of the callback, instead of the normal return. This properly cleans up the temporary memory allocated for the string conversions.
Side point: ftw(3) is much more interesting unix API to call from some FFI layer than qsort(3). And I spent about a year pestering people from Sun with you should implement fts_open(3) and friends because it presents more sane API for FFIs for the same functionality.
It seems Solaris has been adding many BSD and, especially, Linux compatibility APIs lately. It seems too little, too late; or perhaps the initiative is part of their effort to EoL Solaris, providing an upgrade path to Linux.
There's really no reason to pass a rust closure to `qsort` instead of sorting in Rust. That said, if there's demand for real world use cases that require passing Rust closures to C APIs that take only a function pointer and not a data pointer, I'll be happy to write a follow up.
That's still true even if the API takes a separate context pointer that is given to your function as an argument.
There is still a function pointer there, and what you'd like to use as a function pointer is a function in your local language, and that's an object with an environment. Even if some instances of it have no environment, the FFI mechanism might want to tack on its own. For instance, the FFI needs to be able to route the callback to the appropriate function. Whatever function pointer FFI gives to the C function, when the C library calls that function, FFI has to dispatch it back to the original high level function. That requires context. Now that context could be shoehorned into that context parameter, but it could be inconvenient to do so; that parameter belongs to the program and to the conversation that program is having with the C API.
(qsort is really only for C. Other languages can potentially inline the comparison function, so using FFI for that is kind of insane.)
Problem solved.
This is the TXR Lisp interactive listener of TXR 228.
Quit with :quit or Ctrl-D on empty line. Ctrl-X ? for cheatsheet.
1> (with-dyn-lib nil
(deffi qsort "qsort" void ((ptr (array wstr)) size-t size-t closure))
(deffi-cb qsort-cb int ((ptr wstr-d) (ptr wstr-d))))
#:lib-0005
2> (let ((vec #("the" "quick" "brown" "fox"
"jumped" "over" "the" "lazy" "dogs")))
(prinl vec)
(qsort vec (length vec) (sizeof wstr)
[qsort-cb (lambda (a b) (cmp-str a b))])
(prinl vec))
#("the" "quick" "brown" "fox" "jumped" "over" "the" "lazy" "dogs")
#("brown" "dogs" "fox" "jumped" "lazy" "over" "quick" "the" "the")
#("brown" "dogs" "fox" "jumped" "lazy" "over" "quick" "the" "the")
The lambda is pointless; we could create the FFI closure directly from cmp-str with [qsort-cb cmp-str]. It shows more clearly that we can use any closure.Author is hilarious. Who is familiar with that but not c?
This is just like the node.js craze a few years ago - people will rant on trying to justify why you should use rust and write the "rust" way before realising that what they already had worked as intended.
A true replacement for C (when one is finally developed) will remove all of these doubts and back-shadowing behaviour almost instantly (kind of like the react way of ux did)
EDIT: typo
As for C staying around, unfortunately yes, until we get rid of POSIX based OSes, C will be around.
After all we need to keep those <UNIX clone OS> Security conferences alive. /s