Live data from Hacker News

Neat Rust Tricks: Passing Closures to C

blog.seantheprogrammer.com

11–20 of 69 posts

Re: Neat Rust Tricks: Passing Closures to C

#11
You can pass closures to C as C functions in TXR Lisp, a language I created.

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.

Re: Neat Rust Tricks: Passing Closures to C

#12

You can pass closures to C as C functions in TXR Lisp, a language I created. 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…

Here is another example of callbacks at work from the TXR Lisp test suite: using the C library funtion qsort to sort a Lisp array of strings.

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.

Re: Neat Rust Tricks: Passing Closures to C

#13
post #10
post #9

Earlier quoted context omitted.

That's correct - a Rust closure generally [1] can't be converted to a function pointer as it requires both code and state. [1] https://github.com/rust-lang/rust/issues/39817

The whole point of jgtrosh's link is that there is a way to hide data behind a function pointer, so Rust could convert any closure to a function pointer. But it requires writable-and-executable memory, so it's a pretty bad idea (in GCC's implementation, that memory is on the stack, which is an extra bad idea, but i don't think it needs to be).

Definitely.

Technically this can also be done via static code trampolines that are mmap'd as well [1]. That approach has been used on iOS in the past to turn blocks into raw function pointers.

If you have a platform that allows W+X on code (yikes!), you can do [2] as well.

[1] https://github.com/plausiblelabs/plblockimp/blob/master/Sour... [2] https://www.mikeash.com/pyblog/friday-qa-2010-02-12-trampoli...

Re: Neat Rust Tricks: Passing Closures to C

#15
post #2

Is this a neat trick or just standard operating procedure for calling C from ? 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. Maybe I just overcomplicate things ;-)

It does seem quite similar to Haskell FFI code: https://github.com/bobfrank/hasqlite/blob/4e38801d969a43e88b...

The "neat" factor comes from how little type wrangling and unsafe code is needed.

Re: Neat Rust Tricks: Passing Closures to C

#16
post #2

Is this a neat trick or just standard operating procedure for calling C from ? 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. Maybe I just overcomplicate things ;-)

I’d say that “standard procedure” would be to do it the same way as it would be done in C: define a struct, allocate one somewhere, then pass a pointer to it as the data pointer. Using the anonymous struct which represents the closure itself seems like skipping a step, the user doesn’t need to spell out which values are stored in the struct.

Re: Neat Rust Tricks: Passing Closures to C

#19

Now call qsort with a closure.

  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.

Re: Neat Rust Tricks: Passing Closures to C

#20
post #16
post #2

Is this a neat trick or just standard operating procedure for calling C from ? 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. Maybe I just overcomplicate things ;-)

I’d say that “standard procedure” would be to do it the same way as it would be done in C: define a struct, allocate one somewhere, then pass a pointer to it as the data pointer. Using the anonymous struct which represents the closure itself seems like skipping a step, the user doesn’t need to spell out which values are stored in the struct.

If the language supports closures which capture variables from their surrounding environment, there's no way around using "the closure itself" as your data object. After all, "the user" is not expected to "know" what any given closure is capturing from the environment; part of the point of closures is implementing a sort of information hiding.
Post reply on HN