FFI Objects, Structs, and Callbacks

knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
tcc_bind <- Rtinycc::tcc_bind
tcc_callback <- Rtinycc::tcc_callback
tcc_callback_async_drain <- Rtinycc::tcc_callback_async_drain
tcc_callback_close <- Rtinycc::tcc_callback_close
tcc_callback_ptr <- Rtinycc::tcc_callback_ptr
tcc_compile <- Rtinycc::tcc_compile
tcc_ffi <- Rtinycc::tcc_ffi
tcc_source <- Rtinycc::tcc_source
tcc_struct <- Rtinycc::tcc_struct

This vignette covers two patterns that come up quickly in real bindings:

Working with Struct Helpers

Struct helpers are generated from a declarative description. In the example below, Rtinycc creates allocation, field getter, field setter, and free helpers for a simple struct point.

ffi_struct <- tcc_ffi() |>
  tcc_source(
    "
    struct point {
      double x;
      double y;
    };

    double point_norm2(struct point* p) {
      return p->x * p->x + p->y * p->y;
    }
    "
  ) |>
  tcc_struct("point", accessors = c(x = "f64", y = "f64")) |>
  tcc_bind(
    point_norm2 = list(args = list("ptr"), returns = "f64")
  ) |>
  tcc_compile()

pt <- ffi_struct$struct_point_new()
pt <- ffi_struct$struct_point_set_x(pt, 3)
pt <- ffi_struct$struct_point_set_y(pt, 4)

ffi_struct$point_norm2(pt)
ffi_struct$struct_point_free(pt)

This keeps the C layout explicit while still giving you a usable R-facing surface.

Named nested struct fields can also be modeled directly with struct:<name>. Those getters return borrowed nested views and setters copy bytes from a source struct object of the matching nested type.

Registering Callbacks

Callbacks let compiled C code invoke an R function through a generated trampoline. The callback object and the callback pointer play different roles:

cb <- tcc_callback(
  function(x) x * 2,
  signature = "double (*)(double)"
)
cb_ptr <- tcc_callback_ptr(cb)

ffi_cb <- tcc_ffi() |>
  tcc_source(
    "
    double apply_cb(double (*cb)(void* ctx, double), void* ctx, double x) {
      return cb(ctx, x);
    }
    "
  ) |>
  tcc_bind(
    apply_cb = list(
      args = list("callback:double(double)", "ptr", "f64"),
      returns = "f64"
    )
  ) |>
  tcc_compile()

ffi_cb$apply_cb(cb, cb_ptr, 5)
tcc_callback_close(cb)

tcc_callback_close() is recommended when you want deterministic invalidation and prompt release of the preserved R function. If you simply drop all references, finalizers will still clean up the callback eventually.

Async Callback Caveats

callback_async:<signature> is the safe path for worker-thread callbacks, but its contract is narrower than the synchronous trampoline path:

Soundness Notes

The callback contract is deliberately explicit:

That explicitness is part of what keeps Rtinycc predictable as a systems interface rather than a partial compiler front-end.



Try the Rtinycc package in your browser

Any scripts or data that you put into this service are public.

Rtinycc documentation built on April 28, 2026, 1:07 a.m.