future_form_ffi 0.1.0

FFI support for future_form: host-driven polling, opaque handles, and effect slots
Documentation
#![no_std]
//!
//! FFI support for [`future_form`]: host-driven polling, opaque handles,
//! and effect slots.
//!
//! This crate provides the building blocks for FFI bridges that let
//! foreign hosts (Go, Java, Python, C, Swift) drive Rust async state
//! machines without an async runtime.
//!
//! # Core types
//!
//! | Type | Purpose |
//! |------|---------|
//! | [`PollOnce`](poll_once::PollOnce) | Extension trait: poll a boxed future once with a no-op waker |
//! | [`HostHandle`](host_handle::HostHandle) | Thin-pointer wrapper for passing boxed futures through C ABI |
//! | [`AtomicSlot`](atomic_slot::AtomicSlot) | Lock-free, thread-safe slot for a single value |
//! | [`EffectSlot`](effect_slot::EffectSlot) | Shared-state channel for the effect protocol (built on `AtomicSlot`) |
//! | [`EffectHandle`](effect_handle::EffectHandle) | Future handle + stashed effect + context pointer |
//!
//! # Architecture
//!
//! ```text
//! ┌────────────┐                    ┌──────────────────────┐
//! │  FFI Host  │  poll_once()       │  HostHandle<F>       │
//! │  (Go, Java │ ──────────────────>│  or EffectHandle     │
//! │   Python…) │ <──────────────────│                      │
//! │            │  Poll<T>           │                      │
//! │            │                    │  ┌────────────────┐  │
//! │            │  take_effect()     │  │  EffectSlot    │  │
//! │            │ ──────────────────>│  │  <E, R>        │  │
//! │            │ <──────────────────│  └────────────────┘  │
//! │            │  fulfill(resp)     │                      │
//! │            │ ──────────────────>│                      │
//! └────────────┘                    └──────────────────────┘
//! ```
//!
//! # FFI bridge pattern
//!
//! FFI hosts can't receive a Rust `Box<T>` directly — they need a raw
//! pointer. The standard pattern is:
//!
//! 1. **Create**: heap-allocate the handle and convert to a raw pointer
//!    with [`Box::into_raw`](alloc::boxed::Box::into_raw), transferring ownership to the host.
//! 2. **Poll/inspect**: the host passes the pointer back into Rust
//!    bridge functions that reconstruct a reference (`&mut *ptr`).
//! 3. **Free**: the host calls a destructor that reclaims ownership
//!    with [`Box::from_raw`](alloc::boxed::Box::from_raw) and drops the handle.
//!
//! ```rust,ignore
//! // 1. Create — ownership moves to the host
//! let handle = HostHandle::new(fut);
//! let ptr = Box::into_raw(Box::new(handle));  // thin *mut for C ABI
//!
//! // 2. Poll — borrow from the raw pointer
//! let handle = unsafe { &mut *ptr };
//! let result = handle.poll_once();
//!
//! // 3. Free — reclaim and drop
//! unsafe { drop(Box::from_raw(ptr)) };
//! ```
//!
//! [`EffectHandle`](effect_handle::EffectHandle) follows the same
//! pattern, adding a stashed effect and context pointer for the effect
//! protocol.
//!
//! # Example
//!
//! Simple bridge using [`HostHandle`](host_handle::HostHandle):
//!
//! ```rust
//! use core::task::Poll;
//! use future_form::{FutureForm, Sendable};
//! use future_form_ffi::host_handle::HostHandle;
//!
//! let fut = Sendable::from_future(async { 42u64 });
//! let mut handle = HostHandle::new(fut);
//! assert_eq!(handle.poll_once(), Poll::Ready(42));
//! ```
//!
//! Effect protocol using [`EffectSlot`](effect_slot::EffectSlot):
//!
//! ```rust
//! use core::task::Poll;
//! use future_form_ffi::effect_slot::EffectSlot;
//!
//! #[derive(Clone, Debug, PartialEq)]
//! enum Fx { GetTime }
//!
//! #[derive(Debug, PartialEq)]
//! enum Resp { Time(u64) }
//!
//! let slot: EffectSlot<Fx, Resp> = EffectSlot::new();
//! assert_eq!(slot.request(&Fx::GetTime), Poll::Pending);
//! slot.fulfill(Resp::Time(1234));
//! assert_eq!(slot.request(&Fx::GetTime), Poll::Ready(Resp::Time(1234)));
//! ```
//!
//! # Slot ownership
//!
//! An [`EffectSlot`](effect_slot::EffectSlot) is a single
//! request-response channel. If multiple futures share the same slot,
//! they will overwrite each other's pending effects and receive
//! responses intended for other futures. This is memory-safe (all
//! operations are atomic) but semantically incorrect.
//!
//! Two ownership strategies are supported:
//!
//! ## Per-struct slot (simple, one future at a time)
//!
//! Embed the slot in the struct. All futures share it. Only one future
//! may be in flight at a time:
//!
//! ```rust,ignore
//! struct MyService {
//!     state: u64,
//!     effects: EffectSlot<Effect, Response>,
//! }
//! ```
//!
//! ## Per-future `Arc<EffectSlot>` (unlimited concurrent futures)
//!
//! Create a fresh slot for each future. Share it via `Arc` between the
//! async closure and the [`EffectHandle`](effect_handle::EffectHandle).
//! There is no limit on the number of in-flight futures — each one gets
//! its own independent effect channel:
//!
//! ```rust,ignore
//! use alloc::sync::Arc;
//!
//! let slot = Arc::new(EffectSlot::new());
//! let slot_for_future = Arc::clone(&slot);
//!
//! let fut = Box::pin(async move {
//!     let resp = core::future::poll_fn(|_cx| {
//!         slot_for_future.request(&Effect::GetTimestamp)
//!     }).await;
//!     // ...
//! });
//!
//! let handle = EffectHandle::new(HostHandle::new(fut), context);
//! // The bridge stores `slot` alongside the handle (e.g., in a wrapper struct)
//! // so the host can call slot.take_effect() / slot.fulfill().
//! ```
//!
//! The per-future approach has no capacity limit — the host can create
//! as many concurrent futures as memory allows, each with its own
//! effect channel. The cost is one `Arc` allocation per future,
//! negligible alongside the `Box::pin` that already heap-allocates the
//! future. The [`key_value_store`] example demonstrates this pattern
//! with concurrent Get/Put/Delete/List operations. See the [design docs]
//! for a full comparison of ownership strategies including slot
//! registries and fixed-size arrays.
//!
//! [`key_value_store`]: https://codeberg.org/expede/future_form/src/branch/main/future_form_ffi/examples/key_value_store/
//!
//! [design docs]: https://codeberg.org/expede/future_form/src/branch/main/design/host-driven-ffi.md#effect-slot-ownership
//!
//! # Handle storage
//!
//! Rust does not assign or manage future IDs. The
//! [`EffectHandle`](effect_handle::EffectHandle) _is_ the identity —
//! an opaque token bundling the future, its slot, and its context. The
//! host stores these however its concurrency model demands:
//!
//! ## Host-side collection
//!
//! The host keeps a map of handles, keyed however it likes:
//!
//! ```text
//!   Host (Go / Java / Python)
//!   ┌─────────────────────────────────────────────┐
//!   │                                             │
//!   │  handles: Map<FutureId, *mut EffectHandle>  │
//!   │                                             │
//!   │  handle_1 ──► EffectHandle ──► Arc<slot_1>  │
//!   │  handle_2 ──► EffectHandle ──► Arc<slot_2>  │
//!   │  handle_3 ──► EffectHandle ──► Arc<slot_3>  │
//!   │                                             │
//!   └─────────────────────────────────────────────┘
//! ```
//!
//! The `FutureId` is whatever the host wants — an integer counter, a
//! UUID, a task name. It is entirely host-side bookkeeping. Rust does
//! not know or care about it.
//!
//! ## Inline in host tasks
//!
//! Each host-side task/goroutine/thread holds its handle directly — no
//! map needed:
//!
//! ```text
//!   Go goroutine A                Go goroutine B
//!   ┌──────────────────┐          ┌──────────────────┐
//!   │ handle *C.Handle │          │ handle *C.Handle │
//!   └────────┬─────────┘          └────────┬─────────┘
//!            │                             │
//!            ▼                             ▼
//!      EffectHandle                  EffectHandle
//!      └─► Arc<slot_a>              └─► Arc<slot_b>
//! ```
//!
//! No future ID at all — the handle _is_ the identity.
//!
//! ## Event loop with a vec
//!
//! ```text
//!   Host event loop
//!   ┌──────────────────────────────┐
//!   │  active: Vec<EffectHandle>   │
//!   │                              │
//!   │  for handle in &mut active { │
//!   │      match handle.poll() {   │
//!   │          Pending => check fx │
//!   │          Ready   => remove   │
//!   │      }                       │
//!   │  }                           │
//!   └──────────────────────────────┘
//! ```
//!
//! The index in the vec is the implicit ID, or the host uses a slab
//! allocator. When a future completes, the host removes it — the
//! handle drops, its `Arc<EffectSlot>` drops, and the slot is freed.
//!
//! All three patterns work because `EffectHandle` is self-contained:
//! the host never needs to coordinate with Rust about which handle
//! maps to which future. See the [design docs] for more detail.
//!
//! # `no_std` support
//!
//! This crate is `#![no_std]` and requires only `core` + `alloc`. No
//! feature flags, no platform-specific dependencies.
//!
//! [`EffectSlot`](effect_slot::EffectSlot) is built on
//! [`AtomicSlot`](atomic_slot::AtomicSlot), which uses lock-free
//! [`AtomicPtr`](core::sync::atomic::AtomicPtr) swaps for thread
//! safety — no mutex, no poisoning, no global lock contention.

extern crate alloc;

pub mod atomic_slot;
pub mod effect_handle;
pub mod effect_slot;
pub mod host_handle;
pub mod poll_once;