Closure

2 Posts

FFI Callbacks and void *ctx

5 minute

C callback APIs usually look like this:

typedef void (*event_cb_t)(void *ctx, int event);

int device_register_callback(struct device *dev, event_cb_t cb, void *ctx);
void device_unregister_callback(struct device *dev);

The function pointer is the code to run. ctx carries the caller’s environment.

Inside Rust, closures express “code plus environment.” But across a C ABI, a capturing closure cannot directly become a C function pointer. C understands a fixed-ABI function address and a void *ctx.

So FFI callbacks are mainly a lifetime and ownership problem:

Where is the callback object stored?
If C stores ctx, is the Rust owner still alive?
Which thread invokes the callback?
Can C call after unregister?
Can destruction race with a running callback?

C Needs an extern Function, Not a Capturing Closure

A Rust function that captures no environment can be used as a C callback:

Read More

Closures and Callbacks

6 minute

C callbacks usually look like a function pointer plus a context pointer:

typedef void (*event_cb_t)(void *ctx, int event);

void register_event(event_cb_t cb, void *ctx);

cb is the function to call, and ctx is the environment passed back during the call. This pattern is mature, but the old problems remain:

  • the real type of ctx is a convention
  • the lifetime of ctx is a convention
  • whether the callback mutates the environment is a convention
  • whether the callback is called once or many times is a convention
  • thread-safety requirements are a convention

Rust closures solve the same problem: keep the code to run together with the environment it needs.

Read More