Home > Software engineering >  When using bracket with a Ptr as resource, can it be replaced with ForeignPtr?
When using bracket with a Ptr as resource, can it be replaced with ForeignPtr?

Time:06-14

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

  • Related