---
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
