My code uses a resource that can be described as a pointer; I'll use a void
pointer here for simplicity. The resource must be closed after the computation with it finishes, so the Control.Exception.bracket
function is a natural choice to make sure the code won't leak if an error occurs:
run :: (Ptr () -> IO a) -> IO a
run action = bracket acquireResource closeResource action
-- no eta reduction for clarity
The downside of this pattern is that the resource will always be closed after action
completes. AFAIU this means that it isn't possible to do something like
cont <- run $ \ptr -> do
a <- someAction ptr
return (\x -> otherActionUsingResource ptr a x)
cont ()
The resource will already be close by the time cont
is executed. Now my approach is to use a ForeignPtr instead:
run' :: (ForeignPtr () -> IO a) -> IO a
run' action = do
ptr <- acquireResource
foreignPtr <- newForeignPtr closeResourceFunPtr ptr
action foreignPtr
Now it seems that this is roughly equivalent to the first version, minor typing differences and resource closing latency aside. However, I do wonder whether this is true, or if I miss something. Can some error conditions can lead to different outcomes with those two versions? Are ForeignPtr safe to use in this way?
CodePudding user response:
If you want to do this, I'd recommend avoiding that run'
, which makes it look like you're going to close the resource. Do something like this instead.
acquire :: IO (ForeignPtr ())
acquire action = mask $ \unmask -> do
ptr <- unmask acquireResource
newForeignPtr closeResourceFunPtr ptr
The challenge with anything of this sort is that you're leaving it up to the user and/or garbage collector to make sure the resource gets freed. The original Ptr
-based code made the lifespan explicit. Now it's not. Many people believe that explicit lifespans are better for critical resources. What ForeignPtr
gives you, automatic finalization by the GC, these people consider poor design. So think carefully! Is this a cheap resource (like a little bit of malloc
ed memory) that you just want to free eventually? Or is it something expensive (like a file descriptor) that you really want to be sure about?
Side note: Ptr ()
and ForeignPtr ()
aren't very idiomatic. Usually the type argument should be a Haskell type representing whatever is being pointed to.