ffi-error-progress-cancel

Design typed errors, progress streams, and cooperative cancellation for Rust FFI calls in Kotlin and Swift.

2|1|Updated Aug 19, 2026
One-click install
npx skills add https://github.com/po4yka/rust-skills --skill ffi-error-progress-cancel-po4yka
Or copy as Structured Prompt for Agent▼
Please help me install this Agent Skill.
Skill: ffi-error-progress-cancel
Source: https://github.com/po4yka/rust-skills/tree/main/skills/ffi-error-progress-cancel
Command: npx skills add https://github.com/po4yka/rust-skills --skill ffi-error-progress-cancel-po4yka

SYSTEM DOCUMENTATION & REQUIREMENTS

💡 This Skill includes references (resource) components.

What problem does it solve? Long-running Rust operations exposed to mobile apps through UniFFI often ship with mismatched error enums, progress streams that leak workers, and cancel calls that block the UI thread. This Skill treats the error taxonomy, progress stream, and cancellation protocol as one contract so Kotlin and Swift consumers get consistent, safe behavior. ## Core Features & Use Cases - Flat boundary error model: Defines a versioned flat_error UniFFI enum mapped to five native UX buckets, with exhaustive mappers that fail the build when bindings regenerate with new variants. - Progress streaming bridges: Provides callbackFlow with buffer(Channel.CONFLATED) on Kotlin and AsyncThrowingStream with .bufferingNewest(1) on Swift, plus a closed stage enum and determinism guarantees. - Cooperative cancellation: Implements a bounded job registry with pre-cancel reservations, TTL reaping, poison recovery, idempotent non-blocking cancel_job, and partial-output cleanup via temporary-file rename. - Use Case: When wiring a Rust export engine to an Android ViewModel and an iOS SwiftUI screen, use this Skill to ensure a cancelled job maps to CancellationException/CancellationError, leaves no partial file, and never hangs onCleared or deinit. ## Quick Start Ask the agent to review or design the error, progress, and cancellation contract for a UniFFI-exported long-running Rust call consumed from Kotlin or Swift.

Frequently Asked Questions about ffi-error-progress-cancel

High-intent search queries and answers about installing and using this skill.

FAQPage Schema
How do I cancel a long-running Rust UniFFI call from Kotlin or Swift?▼

Expose an idempotent, non-blocking cancel_job(job_id) export that sets an AtomicBool flag checked at stage boundaries. On Kotlin call it only from awaitClose; on Swift only from onTermination. Never cancel by panicking or killing a thread.

How to stream progress from Rust to Kotlin Flow and Swift AsyncSequence?▼

Declare a ProgressListener foreign trait and emit ProgressEvent with job_id, stage, and fraction. Bridge it with callbackFlow plus buffer(Channel.CONFLATED) on Kotlin, and AsyncThrowingStream with .bufferingNewest(1) on Swift, so slow consumers never block the engine worker.

Does UniFFI async fn cancellation reach Rust from Swift or Kotlin?▼

No. UniFFI does not forward platform cancellation to Rust as a cooperative cancel signal. Kotlin frees the future at its current await point and Swift keeps awaiting, so keep an explicit cancel_job export even for async fn operations.

Why does my Swift app crash when Rust cancel_job panics?▼

UniFFI turns a panic inside a non-throwing Swift call into a fatal error Swift cannot catch. Keep cancel_job panic-free by recovering lock poison with PoisonError::into_inner instead of lock().expect, and map poison to a typed error on throwing paths.

How should Rust FFI errors map to Kotlin and Swift types?▼

Use a flat UniFFI error enum with one variant per core kind, then write exhaustive mappers with no else or default arm. Map Cancelled to CancellationException or CancellationError, and route panics and binding skew to an engine-bug bucket.

What are the limitations of callbackFlow for Rust progress events?▼

callbackFlow requires awaitClose or it throws IllegalStateException, and a full buffer makes trySend drop events. Fuse it with buffer(Channel.CONFLATED) so stale progress is replaced, and treat progress as observational since event counts may vary across runs.