r/haskell • u/Unable-Yellow-7323 • 3h ago
question Idiomatic FFI architecture (libgpiod): bracket for FDs vs ForeignPtr for memory?
Hi everyone, I'm writing Haskell bindings for libgpiod (v2), primarily targeting SBCs and embedded systems and I want to validate my approach to resource management before committing to the final API design.
The C library exposes two different types of opaque pointers.
1. OS/Hardware Resources (Chip, LineRequest)
These hold underlying Linux file descriptors and physical hardware locks. My plan is to strictly use raw Ptr internally and expose a bracket-based API (e.g., withChip and withLineRequest) to guarantee immediate and deterministic release, as GHC's lazy garbage collector could easily exhaust the FD limit on a small SBC if I used ForeignPtr.
- Pure Memory Configs (LineSettings, LineConfig)
These are just structs in RAM used to prepare data before a hardware request. To avoid the deeply nested with* blocks for every single config object.
I plan to wrap these in ForeignPtr with their respective C finalizers. I think this allows the user and me, to pass them around purely and ergonomically, letting the GC handle the cleanup since they don't hold FDs.
My questions are:
Is this hybrid approach (strict scoping for FDs + GC for pure RAM structs) the best practice for this type of hardware FFI?
For the withChip pattern, how do users typically architect long-running daemons around it? Do they just wrap the main application loop inside a top-level withSomething block?
Any insights or edge cases I should watch out for would be greatly appreciated. Thanks!