Questions about FFI, ForeignPtr, and Opaque Types

When I needed to work with a lot of this kind of “withPtr” FFI, I made a newtype for it.

NestedIO is for IO stuff that needs to operate in these nested “with” contexts.

This newtype allows me to implement a wrapper for any of the “withPtr”-style functions with nest1 :: (forall r. (a -> IO r) -> IO r) -> NestedIO a

With this I can take withText :: Text -> NestedIO FgnStringLen and then use traverse to do a version of what you’re asking about:

traverse withText :: [Text] -> NestedIO [FgnStringLen]

Then you can see this in a larger example like in withRawIrcMsg where a great many “withPtr” like operations are all chained together.

1 Like

Indeed - that is why I avoided unsafely reaching in to grab the pointer via unsafeUseAsCString return - I probably should have specified ‘collected’ instead of ‘move(d)’. (Does getting garbage-collected count as moving?)

This is most excellent - I already have a WithPtr type-alias, which is but a step away from it:

type WithPtr typ ptr = (forall a . typ -> (ptr -> IO a) -> IO a)

I shall definitely have to give some thought towards refining things further. I especially like the trick with traverse :slight_smile:

The pointer can’t be collected when using it with that operation. That’s not how it is unsafe. It’s the same mechanism as withPtr or alloca.

That was a small joke in reference to my linguistic faux pas, equivalent to calling deleting a file “moving it to /dev/null”, that sort of thing. I understand how unsafeUseAsCString is unsafe - it allows breaking referential transparency (editing the bytes edits the bytestring and any other sharing the same backing memory) and the pointer’s lifetime is scoped to wrapped action (so don’t use the pointer after).

Apologies for any confusion caused.

I prefer this to having a newtype over Ptr () (that would be void* and you can pull it out thin air) or Ptr Uninhabited.

It protects the pointer, removing the pointer details from public API while keeping it in one piece, without splitting into two or three distinct entities only to recombine them back again.

2 Likes

This is how I solved a similar problem in my libtelnet binding:

-- | Collect '[ByteString]' into a temporary array of strings in a
-- 'Ptr CString', for passing to C functions.
useAsCStrings :: [ByteString] -> (Ptr CString -> IO a) -> IO a
useAsCStrings list f = go list [] where
  go [] css = withArray (reverse css) f
  go (bs:bss) css = B.useAsCString bs $ \cs -> go bss (cs:css)
1 Like