future_form_ffi 0.1.0

FFI support for future_form: host-driven polling, opaque handles, and effect slots
Documentation
#!/usr/bin/env python3
"""End-to-end demonstration: Python host drives a Rust async state machine
via future_form's PollOnce trait.

Uses ctypes (stdlib) to call the same C ABI that the Go host uses.

Build the Rust cdylib first, then:
    python3 counter_host.py
"""

import ctypes
import os
import platform
import sys
from ctypes import c_uint8, c_uint64, c_void_p
from pathlib import Path


# -- Load the Rust cdylib ----------------------------------------------------

def _find_lib():
    bridge_dir = Path(__file__).resolve().parent.parent / "rust_bridge" / "target" / "debug"
    if platform.system() == "Darwin":
        return bridge_dir / "libffi_bridge.dylib"
    return bridge_dir / "libffi_bridge.so"


lib = ctypes.CDLL(str(_find_lib()))


# -- FfiPollResult layout: { uint8_t ready; padding[7]; uint64_t value } ------

class FfiPollResult(ctypes.Structure):
    _fields_ = [
        ("ready", c_uint8),
        ("_padding", c_uint8 * 7),
        ("value", c_uint64),
    ]


# -- Declare function signatures ----------------------------------------------

lib.counter_new.argtypes = [c_uint64]
lib.counter_new.restype = c_void_p

lib.counter_free.argtypes = [c_void_p]
lib.counter_free.restype = None

lib.counter_next_value.argtypes = [c_void_p]
lib.counter_next_value.restype = c_void_p

lib.counter_add.argtypes = [c_void_p, c_uint64]
lib.counter_add.restype = c_void_p

lib.future_poll.argtypes = [c_void_p]
lib.future_poll.restype = FfiPollResult

lib.future_free.argtypes = [c_void_p]
lib.future_free.restype = None


# -- Sans-IO polling loop — the Python host is the executor -------------------

def poll(future_handle):
    while True:
        result = lib.future_poll(future_handle)
        if result.ready != 0:
            return result.value
        # In a real app: fulfill I/O, check queues, etc.


# -- Test harness -------------------------------------------------------------

passed = 0
failed = 0


def check(name, got, want):
    global passed, failed
    if got == want:
        print(f"  PASS  {name}: got {got}")
        passed += 1
    else:
        print(f"  FAIL  {name}: got {got}, want {want}")
        failed += 1


def main():
    global passed, failed

    print("=== Python <-> Rust FFI (Sendable + PollOnce) e2e test ===")
    print()

    # --- Test 1: next_value (10 -> 11) ---
    counter = lib.counter_new(10)

    fut = lib.counter_next_value(counter)
    val = poll(fut)
    lib.future_free(fut)
    check("next_value(10)", val, 11)

    # --- Test 2: add (10 + 32 = 42) ---
    fut = lib.counter_add(counter, 32)
    val = poll(fut)
    lib.future_free(fut)
    check("add(10, 32)", val, 42)

    # --- Test 3: different counter ---
    lib.counter_free(counter)
    counter = lib.counter_new(100)

    fut = lib.counter_next_value(counter)
    val = poll(fut)
    lib.future_free(fut)
    check("next_value(100)", val, 101)

    fut = lib.counter_add(counter, 900)
    val = poll(fut)
    lib.future_free(fut)
    check("add(100, 900)", val, 1000)

    lib.counter_free(counter)

    # --- Summary ---
    print()
    print(f"--- {passed} passed, {failed} failed ---")

    if failed > 0:
        sys.exit(1)


if __name__ == "__main__":
    main()