Internals

This article is an implementation note, not a compatibility guarantee.

Dataflow

At runtime, all operations go through three explicit stages:

  1. R wrapper builds a typed argument object and lightweight error checks,
  2. Rust layer executes async OpenDAL operations,
  3. R result or C result object surfaces either a value or an opendalErrorValue.

OpendalFs is the stable user-facing handle. Async completion is represented by OpendalAio, which carries completion state and result storage.

Async and thread safety

Background work runs in a Rust Tokio runtime. Callbacks and completion paths do not call into the R C API directly; this is intentional, so synchronization and waiting are explicit on the R side (call_aio(), collect_aio(), cv_wait(), aio_monitor()).

Serialization and codecs pipeline

The package keeps bytes as the storage contract and applies optional transforms in this order:

  • serializer for R object/text materialization (raw, text, serial),
  • optional native codec (identity, gzip, zlib),
  • backend transport.

That means byte-level options remain explicit and inspectable in both sync and async paths.

Layer controls

Layer constructors are opt-in and composable:

  • runtime_config(threads = ...)
  • layer_concurrent_limit(max = ...)
  • layer_timeout(request_timeout = ..., io_timeout = ...)

All are attached at handle construction so behavior is consistent per OpendalFs.

API boundaries by function family

Public functions are split by surface, with async forms sharing the same shape as sync forms where practical:

  • filesystem operations: read/write/stat/exists/list/ls/delete/copy/rename/mkdir
  • stream readers/writers: fs_read_iter(), fs_write_iter()
  • monitors and collection: collect_aio(), read_monitor(), aio_monitor()

The C header mirrors this boundary as a separate pure-C API for downstream users who need async bytes or completions without R-specific dependencies.