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