--- epic: 1 story: 1.5 title: "C FFI Layer (for Swift)" status: draft --- ## Epic 1 — Core Calculation Engine (Rust Crate) **Goal:** Build `calcpad-engine` as a standalone Rust crate that powers all platforms. This is the foundation. ### Story 1.5: C FFI Layer (for Swift) As a macOS/iOS developer integrating CalcPad, I want a stable C ABI with explicit memory ownership and no panics crossing the FFI boundary, So that Swift can safely call into the Rust engine without undefined behavior. **Acceptance Criteria:** **Given** a Swift caller invoking `calcpad_eval_line` with a C string input **When** the function executes **Then** it returns a pointer to a `CalcResult` struct (or JSON-serialized string) allocated on the Rust heap **And** the caller is responsible for freeing the result via `calcpad_free_result` **Given** a Swift caller invoking `calcpad_eval_sheet` with multiple lines **When** the function executes **Then** it returns an array of results corresponding to each line **And** variable assignments on earlier lines are visible to later lines **Given** an expression that would cause a Rust panic (e.g., internal bug) **When** the FFI function is called **Then** `catch_unwind` intercepts the panic **And** an error result is returned instead of unwinding into Swift **Given** the FFI result is serialized as JSON **When** the result crosses the FFI boundary **Then** the JSON schema is versioned so Swift can handle backward-compatible changes **And** the JSON includes result type, display value, raw value, and any error information **Given** a `CalcResult` pointer returned from `calcpad_eval_line` **When** the Swift caller calls `calcpad_free_result` with that pointer **Then** the Rust allocator deallocates the memory **And** no double-free or use-after-free is possible from correct usage **Given** a null or invalid pointer passed to `calcpad_free_result` **When** the function is called **Then** it safely handles the null/invalid input without crashing **And** no undefined behavior occurs